Posts by author:

josefchutka

What fontName to use with embedding?

by josefchutka on March 10, 2010

This article extends “Embedding fonts bold vs. black” post. Sometimes it may be tricky to guess correct fontName with your font. Compilator works with different fontNames than what Flash IDE shows you. Lets say you want to embed fonts from .swf (library) file into .css file. In Flash IDE, properties panel for TextInput, character Family and Style stand for something totaly different that what you gonna need with correct fontName value later in .css file. In fact solution is very easy.

For example, for Helvetica font family, Flash IDE offers one Family called “Helvetica Neue LT Pro” with different styles “65 Medium”, “55 Roman”:

Here comes the tricky part. When it comes to .css you may want to use:

@font-face
{
    src: url("assets/fonts.swf");
    fontFamily: "Helvetica Neue LT Pro";
    fontStyle: "65 Medium";
}

… but compiler would stop you saying:

font 'Helvetica Neue LT Pro' with normal weight and regular style not found
Unable to transcode assets/fonts.swf

… or something similar.

So how to find out what is the correct fontFamily for compilator? Thankfully, there is an easy help. Locate your .ttf (.otf) file and open it. In my case I have used default system font viewer (Windows XP), but I am sure you can use any solid font viewer application:

…and there you find your familyName. Now you can embed fonts:

@font-face
{
    src: url("assets/fonts.swf");
    fontFamily: "HelveticaNeueLT Pro 65 Md";
}

@font-face
{
    src: url("assets/fonts.swf");
    fontFamily: "HelveticaNeueLT Pro 55 Roman";
    fontWeight: bold;
}

Notice there is different fontFamily and fontWeight (normal vs. bold) used for each font. If you try to use something else (change fontWeight) with these font-face specifications, compiler would stop you again. The fontFamily thing seems to be clear, but for now the thing with fontWeight remains mystery for me (viewer is not saying a thing about it). Flash IDE font family and style are also suggested somehow mysteriously.

Inviting Friends into Facebook Application

by josefchutka on March 9, 2010

Those are hard times. Facebook discontinued support for Notifications.send method March 1, 2010. Calling this method returns error code 3 (Unknown method). Instead, developers are directed to use other communication channels. From those some are not yet even a part of facebook-actionscript-api yet (dashboard) other not published from facebook (invites) and those that may work via api are experimental and may change any day (activities).

Excluding streams as communication channel, the only usable way, these days, to acquire some new facebook application users is using <Fb:request-form> (part of FBML). When your application is IFrame, you may find interesting that you are able to use FBML within your HTML files. In order to make this happen, you have to use <Fb:serverFbml> (renders the FBML on a Facebook server inside an iframe). Now, lets see how to open request form in your Facebook Iframe application:

First thing you need is Cross Domain Communication Channel file . It is simple static file, you should name it xd_receiver.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Cross-Domain Receiver Page</title>
</head>
<body>
    <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js?2" type="text/javascript"></script>
</body>
</html>

now your application index.html file with request form:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
<head></head>
<body>
<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>
<script type="text/javascript">
	window.onload = function()
	{
		FB_RequireFeatures(["XFBML"], function()
		{
			FB.Facebook.init("*YOUR*API*KEY*HERE*", "xd_receiver.htm");
		});
	};
</script>

<fb:serverFbml style="width: 750px;">
	<script type="text/fbml">
		<fb:fbml>
			<fb:request-form action="http://www.google.com" method="POST" invite="true" type="TestApp"
				content="Join my app &lt;fb:req-choice url=&quot;http://apps.facebook.com/facebookyoztest/&quot; label=&quot;Go Test app&quot; /&gt;">
				<fb:multi-friend-selector showborder="false" actiontext="Invite your friends to use Test app.">
			</fb:request-form>
		</fb:fbml>
	</script>
</fb:serverFbml>
</body>
</html>

Place both files in the same folder (or specify xd_receiver.htm path correctly in FB.Facebook.init() function). Now make sure you setup your application correctly:

Canvas Callback URL: http://facebook.yoz.sk/Test/
Connect URL: http://facebook.yoz.sk/Test/
Base Domain: yoz.sk
Canvas URL: http://apps.facebook.com/facebookyoztest/
FBML/iframe: iframe
Application Type: Website

Now request form should be generated when you visit canvas url.

Important:

  • Your application must be iframe type
  • URLs used in this article are not to be used in your apps
  • This article was written and is valid on March 9, 2010

Continue reading:

Fixing Z-sorting in Papervision 3D (update)

by josefchutka on March 4, 2010

It may be very tricky thing to get papervision 3d scene rendered correctly event for the simpliest objects. In my case, I have created simple 3d scene with few primitive objects (8 cubes). Cubes are positioned close to each other to create shape of bigger cube + some small space between. Cubes are added into wrapping DisplayObject3D, later wrapper is added to scene in order to make it easy to rotate cubes as a group. Camera targets the center of the scene. In the demo, there are 3 sliders to rotate wrapping object in each x, y, z axis + two buttons that rotates wrapper in the problematic possition (based on z-sorting fix approach). Finally I managet it to work, you can follow my approaches below. Notice the changes are not cummulative, each approach comes from original Application.mxml. All of the ideas collected online from blogs, forums etc.

Application.mxml code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    applicationComplete="init()" enterFrame="loop()"
    frameRate="40">
<mx:Script>
<![CDATA[
    import org.papervision3d.objects.DisplayObject3D;
    import org.papervision3d.objects.primitives.Cube;
    import org.papervision3d.materials.ColorMaterial;
    import org.papervision3d.materials.utils.MaterialsList;
    import org.papervision3d.render.BasicRenderEngine;
    import org.papervision3d.cameras.Camera3D;
    import org.papervision3d.scenes.Scene3D;
    import org.papervision3d.view.Viewport3D;

    private var viewport:Viewport3D;
    private var scene:Scene3D = new Scene3D();
    private var camera:Camera3D = new Camera3D(60, 10, 1000);
    private var renderer:BasicRenderEngine= new BasicRenderEngine();
    private var wrapper:DisplayObject3D = new DisplayObject3D();

    private var black:ColorMaterial = new ColorMaterial(0x000000, 1);
    private var white:ColorMaterial = new ColorMaterial(0xffffff, 1);

    private function init():void
    {
        viewport = new Viewport3D(width, height, false);
        container.addChild(viewport);

        black.opposite = true;
        white.opposite = true;

        var materialList:MaterialsList = new MaterialsList();
        materialList.addMaterial(black, "top");
        materialList.addMaterial(black, "bottom");
        materialList.addMaterial(white, "left");
        materialList.addMaterial(white, "right");
        materialList.addMaterial(black, "front");
        materialList.addMaterial(black, "back");

        var cube:Cube;
        for(var x:uint = 0; x < 2; x++)
        for(var y:uint = 0; y < 2; y++)
        for(var z:uint = 0; z < 2; z++)
        {
            cube = new Cube(materialList, 100, 100, 100, 1, 1, 1, Cube.ALL);
            cube.x = x * 100 * 1.05;
            cube.y = y * 100 * 1.05;
            cube.z = z * 100 * 1.05;
            wrapper.addChild(cube);
        }

        scene.addChild(wrapper);

        camera.lookAt(cube);
        camera.zoom = 100;
    }

    private function loop():void
    {
        if(!viewport)
            return;

        wrapper.rotationX = rx.value;
        wrapper.rotationY = ry.value;
        wrapper.rotationZ = rz.value;

        renderer.renderScene(scene, camera, viewport);
    }

    private function position1():void
    {
        rx.value = 216;
        ry.value = 101;
        rz.value = 0;
    }

    private function position2():void
    {
        rx.value = 333;
        ry.value = 19;
        rz.value = 360;
    }
]]>
</mx:Script>
<mx:UIComponent id="container" width="100%" height="100%" />
<mx:HBox>
    <mx:VSlider id="rx" value="216" minimum="0" maximum="360" liveDragging="true"/>
    <mx:VSlider id="ry" value="101" minimum="0" maximum="360" liveDragging="true"/>
    <mx:VSlider id="rz" value="0" minimum="0" maximum="360" liveDragging="true"/>
    <mx:VBox>
        <mx:Button label="position 1" click="position1()" />
        <mx:Button label="position 2" click="position2()" />
    </mx:VBox>
</mx:HBox>
</mx:Application>

renders corrupted on this position:


… then I defined camera clipping, but no success (someone’s suggestion, but it seems clipping is to be used for different purpose):

camera.useClipping = true; // before line 57


… adding quarterFaces(), some more vertices for each vertex is not gonna save the day:

cube.quarterFaces(); // before line 50


… adding ViewportLayer-s for each object fixed first problematic view, but corrupted another:

import org.papervision3d.view.layer.ViewportLayer;
...
var viewportLayer:ViewportLayer; // before line 42
...
viewportLayer = viewport.getChildLayer(cube, true); // before line 51
viewportLayer.addDisplayObject3D(cube);


… useOwnContainer should (and does) work the same as adding ViewportLayers:

cube.useOwnContainer = true; // before line 50


… finally, layering + sortMode seems to work fine, and fast :-)

import org.papervision3d.view.layer.util.ViewportLayerSortMode;
...
viewport.containerSprite.sortMode = ViewportLayerSortMode.ORIGIN_SORT; // before line 28
...
cube.useOwnContainer = true; // before line 50


update Mar 5, 2010: … another successful attempt:

cube.meshSort = DisplayObject3D.MESH_SORT_FAR; // before line 50


… ultimate method for correct z-sorting and rendering is in using slower QuadrantRenderEngine :-)

import org.papervision3d.render.QuadrantRenderEngine;
...
private var renderer:QuadrantRenderEngine //replace line 19
        = new QuadrantRenderEngine(QuadrantRenderEngine.CORRECT_Z_FILTER);


Where to go from where:

Pixel Bender Disco

by josefchutka on February 18, 2010

While playing with pixel bender, I created some simple sahders that when combined, reminds me of winamp visualizations effects. Feel free to use any in your own projects.

Click and drag mouse to make some disco effects. (wait while page header flash finishes loading so it runs smoothly)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    enterFrame="enterFrame()" applicationComplete="init()"
    backgroundColor="#ffffff" frameRate="50"
    mouseMove="mouseMoveHandler(event)"
    mouseDown="mouseDownHandler(event)"
    mouseUp="mouseUpHandler(event)">
<mx:Script>
<![CDATA[
    private var drawing:Boolean = false;
    private var color:uint = 0x0;
    private var bitmap:Bitmap = new Bitmap();
    private var lineX:Number = 0;

    [Bindable]
    private var fps:Number = 0;
    private var fpsDate:Date = new Date();
    private var fpsFrames:uint = 0;

    [Bindable]
    private var rotationTime:uint = 0;

    [Bindable]
    private var blurTime:uint = 0;

    [Bindable]
    private var colorizeTime:uint = 0;

    [Bindable]
    private var twirlTime:uint = 0;

    [Embed(source="../pbj/rotation.pbj", mimeType="application/octet-stream")]
    private static const ROTATION_CLASS:Class;
    private static const ROTATION_SHADER:Shader = new Shader(new ROTATION_CLASS());

    [Embed(source="../pbj/blur.pbj", mimeType="application/octet-stream")]
    private static const BLUR_CLASS:Class;
    private static const BLUR_SHADER:Shader = new Shader(new BLUR_CLASS());

    [Embed(source="../pbj/colorize.pbj", mimeType="application/octet-stream")]
    private static const COLORIZE_CLASS:Class;
    private static const COLORIZE_SHADER:Shader = new Shader(new COLORIZE_CLASS());

    [Embed(source="../pbj/twirl.pbj", mimeType="application/octet-stream")]
    private static const TWIRL_CLASS:Class;
    private static const TWIRL_SHADER:Shader = new Shader(new TWIRL_CLASS());

    private function init():void
    {
        placeholder.addChild(bitmap);
    }

    private function mouseDownHandler(event:MouseEvent):void
    {
        drawing = true;
        color = Math.random() * 0xffffff;
    }

    private function initGraphics():void
    {
        container.graphics.clear();
        container.graphics.moveTo(mouseX, mouseY);
        container.graphics.lineStyle(10, color, 1);
    }

    private function mouseUpHandler(event:MouseEvent):void
    {
        drawing = false;
    }

    private function mouseMoveHandler(event:MouseEvent):void
    {
        if(!drawing)
            return;
        container.graphics.lineTo(event.localX, event.localY);
    }

    private function countFps():void
    {
        if(++fpsFrames < 10)
            return;
        fps = Math.round(1 / (new Date().time - fpsDate.time) * 10000);
        fpsDate = new Date();
        fpsFrames = 0;
    }

    private function get w():Number
    {
        return container.width;
    }

    private function get h():Number
    {
        return container.height;
    }

    private function enterFrame():void
    {
        countFps();

        if(!container || !container.width)
            return;

        var bitmapData:BitmapData = new BitmapData(w, h, true, 0x000000);
        bitmapData.draw(bitmap);
        if(rotationCheckBox.selected)
            addRotation(bitmapData, 0.1);
        if(blurCheckBox.selected)
            addBlur(bitmapData);
        if(colorizeCheckBox.selected)
            addColorize(bitmapData, color);
        if(twirlCheckBox.selected)
        {
            var radius:Number = Math.min(w / 2, h / 2);
            addTwirl(bitmapData, 10, new Point(w / 4, h / 2), radius);
            addTwirl(bitmapData, -10, new Point(w / 4 * 3, h / 2), radius);
        }

        addAutoFill();
        bitmapData.draw(container);

        bitmap.bitmapData = bitmapData;
        initGraphics();
    }

    private function addAutoFill():void
    {
        lineX = (lineX > w) ? 0 : (lineX + 15);
        container.graphics.moveTo(lineX - 15, h / 3 * 2);
        container.graphics.lineTo(lineX, h / 3 * 2);
    }

    private function addRotation(bitmapData:BitmapData, rotation:Number):void
    {
        rotationTime = new Date().time;
        ROTATION_SHADER.data.src.input = bitmapData;
        ROTATION_SHADER.data.src.width = w;
        ROTATION_SHADER.data.src.height = h;
        ROTATION_SHADER.data.rotation.value = [rotation];
        ROTATION_SHADER.data.tx.value = [w / 2];
        ROTATION_SHADER.data.ty.value = [h / 2];

        var job:ShaderJob = new ShaderJob(ROTATION_SHADER, bitmapData);
        job.start(true);
        rotationTime = new Date().time - rotationTime;
    }

    private function addBlur(bitmapData:BitmapData):void
    {
        blurTime = new Date().time;
        BLUR_SHADER.data.src.input = bitmapData;
        BLUR_SHADER.data.src.width = w;
        BLUR_SHADER.data.src.height = h;

        var job:ShaderJob = new ShaderJob(BLUR_SHADER, bitmapData);
        job.start(true);
        blurTime = new Date().time - blurTime;
    }

    private function addColorize(bitmapData:BitmapData, color:uint):void
    {
        colorizeTime = new Date().time;
        var r:Number = ((color >> 16) & 0xFF) / 0xFF - 0.5;
        var g:Number = ((color >> 8) & 0xFF) / 0xFF - 0.5;
        var b:Number = (color & 0xFF) / 0xFF - 0.5;

        COLORIZE_SHADER.data.src.input = bitmapData;
        COLORIZE_SHADER.data.src.width = w;
        COLORIZE_SHADER.data.src.height = h;
        COLORIZE_SHADER.data.color.value = [r / 40, g / 40, b / 40];

        var job:ShaderJob = new ShaderJob(COLORIZE_SHADER, bitmapData);
        job.start(true);
        colorizeTime = new Date().time - colorizeTime;
    }

    private function addTwirl(bitmapData:BitmapData, angle:Number,
        center:Point, radius:Number):void
    {
        twirlTime = new Date().time;
        TWIRL_SHADER.data.oImage.input = bitmapData;
        TWIRL_SHADER.data.oImage.width = w;
        TWIRL_SHADER.data.oImage.height = h;
        TWIRL_SHADER.data.radius.value = [radius];
        TWIRL_SHADER.data.center.value = [center.x, center.y];
        TWIRL_SHADER.data.twirlAngle.value = [angle];

        var job:ShaderJob = new ShaderJob(TWIRL_SHADER, bitmapData);
        job.start(true);
        twirlTime = new Date().time - twirlTime;
    }
]]>
</mx:Script>
<mx:UIComponent width="100%" height="100%" id="placeholder" />
<mx:Container id="container" width="100%" height="100%" backgroundAlpha="0"/>
<mx:VBox paddingLeft="10" paddingTop="10">
    <mx:Text text="fps: {fps} / 50" />
    <mx:HBox>
        <mx:CheckBox id="rotationCheckBox" selected="true"/>
        <mx:Label text="rotation {rotationTime} ms" />
    </mx:HBox>
    <mx:HBox>
        <mx:CheckBox id="blurCheckBox" selected="true"/>
        <mx:Label text="blur {blurTime} ms" />
    </mx:HBox>
    <mx:HBox>
        <mx:CheckBox id="colorizeCheckBox" selected="true"/>
        <mx:Label text="colorize {colorizeTime} ms" />
    </mx:HBox>
    <mx:HBox>
        <mx:CheckBox id="twirlCheckBox" selected="false"/>
        <mx:Label text="twirl {twirlTime} ms" />
    </mx:HBox>
</mx:VBox>
</mx:Application>

Rotation pixel bender

<languageVersion : 1.0;>

kernel RotationFilter
<
	namespace : "sk.yoz";
	vendor : "Yoz";
	version : 1;
	description : "Image rotation over transform point";
>
{
	input image4 src;
	output pixel4 dst;

	parameter float tx
	<
		minValue: float(0.0);
		maxValue: float(4096.0);
		defaultValue: float(0.0);
		description: "The amount of movement along the x axis to the right, in pixels.";
	>;

	parameter float ty
	<
		minValue: float(0.0);
		maxValue: float(4096.0);
		defaultValue: float(0.0);
		description: "The amount of movement down along the y axis, in pixels.";
	>;

	parameter float rotation
	<
		minValue: float(0.0);
		maxValue: float(4096.0);
		defaultValue: float(0.0);
		description: "Rotation in radians";
	>;

	void evaluatePixel()
	{
		float2 pos = outCoord();
		float rc = cos(rotation);
		float rs = sin(rotation);
		float dx = pos.x - tx;
		float dy = pos.y - ty;
		dst = sampleNearest(src, float2(
			tx + rc * dx + rs * dy,
			ty + rc * dy - rs * dx));
	}
}

Blur pixel bender

<languageVersion : 1.0;>

kernel BlurFilter
<
	namespace : "sk.yoz";
	vendor : "Yoz";
	version : 1;
	description : "Simple blur";
>
{
	input image4 src;
	output pixel4 dst;

	void evaluatePixel()
	{
		float2 pos = outCoord();

		dst = 1.0 / 9.0 * (
			sampleNearest(src, pos - float2(-1.0, -1.0))
			+ sampleNearest(src, pos - float2(0.0, -1.0))
			+ sampleNearest(src, pos - float2(1.0, -1.0))
			+ sampleNearest(src, pos - float2(-1.0, 0.0))
			+ sampleNearest(src, pos - float2(0.0, 0.0))
			+ sampleNearest(src, pos - float2(1.0, 0.0))
			+ sampleNearest(src, pos - float2(-1.0, 1.0))
			+ sampleNearest(src, pos - float2(0.0, 1.0))
			+ sampleNearest(src, pos - float2(1.0, 1.0)));
	}
}

Colorize pixel bender

<languageVersion : 1.0;>

kernel ColorizeFilter
<
	namespace : "sk.yoz";
	vendor : "Yoz";
	version : 1;
	description : "Simple colorize";
>
{
	input image4 src;
	output pixel4 dst;

	parameter float3 color
	<
		minValue: float3(-1.0, -1.0, -1.0);
		maxValue: float3(1.0, 1.0, 1.0);
		defaultValue: float3(0.0, 0.0, 0.0);
		description: "Color";
	>;

	void evaluatePixel()
	{
		dst = sampleNearest(src, outCoord());
		dst.r += color.r;
		dst.g += color.g;
		dst.b += color.b;
	}
}

Twirl pixel bender effect is available for download here

Inline Images

by josefchutka on February 16, 2010

Have you ever heard about inline images? Inline images use the data URI scheme to embed images directly within web pages. As defined by RFC 2397, data URIs are designed to embed small data items as “immediate” data, as if they were referenced externally. This basicaly means, you can insert base64 encoded byte code of any image directly into html file. Using inline images saves HTTP requests over externally referenced objects. Data URIs can potentially store any type of data, not just images!

This example creates snapshot of flash application (on left side) and dynamicly (using javascript) creates inline image (right side) with base64 encoded bytes in src attribute. (right side is html <img>)

Application source:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
    enterFrame="enterFrame()" borderStyle="solid" borderColor="#000000"
    backgroundColor="#ffffff">
<mx:Script>
<![CDATA[
    import com.adobe.images.PNGEncoder;
    import com.dynamicflash.util.Base64;

    private function enterFrame():void
    {
        if(!container || !container.width)
            return;

        container.graphics.beginFill(Math.random() * 0xffffff, Math.random());
        container.graphics.drawCircle(
            Math.random() * container.width,
            Math.random() * container.height, Math.random() * 30);

    }

    private function makeSnapshot(source:DisplayObject):void
    {
        var script:String = 'var img = document.getElementById("img");'
            + 'if(!img)'
            + '{'
                + 'img = document.createElement("IMG");'
                + 'img.id="img";'
                + 'document.body.appendChild(img);'
            + '}'
            + 'img.src="' + getSrc(source) +'";';
        ExternalInterface.call("function(){" + script + "}");
    }

    private function getByteArray(source:DisplayObject):ByteArray
    {
        var bitmapData:BitmapData = new BitmapData(source.width, source.height);
        bitmapData.draw(source);
        var bitmap:Bitmap = new Bitmap(bitmapData);
        return PNGEncoder.encode(bitmapData);
    }

    private function getSrc(source:DisplayObject):String
    {
        var byteArray:ByteArray = getByteArray(source);
        return 'data:image/png;base64,'
            + Base64.encodeByteArray(byteArray);
    }
]]>
</mx:Script>
<mx:Container id="container" width="100%" height="100%"/>
<mx:Button label="Make Snapshot" click="makeSnapshot(this)"/>
</mx:Application>

Data URLs save HTTP requests. When combined with CSS sprites, data URLs can save numerous HTTP requests. It would be interesting to see if data URLs can be combined with USEMAPS or make a data URL CSS sprite. Data URLs are a convenient way to create self-enclosed web pages that don’t rely on external objects to render.

  • Save HTTP requests, avoids adding to object overhead
  • Save concurrent thread – browsers default to two simultaneous connections per hostname
  • HTTPS requests are simplified and performance improved

Well, for now I have no idea how can flash benefit from data urls :-) but if I come to something I let you know.

Processing Audio in ActionScript 3 and Pixel Bender

by josefchutka on February 16, 2010

Recently I was trying to make some noise with flash, and soon I realized that audio processing is not that easy thing as it may look like. Anyway here is a list of some interesting articles and examples with gain, equalizer, pitch, effects, pixel bender, I have came across while gathering information.

Volume, Equalizer, Effects, Pitch

Spectrum Analyzer

Theory and Hints

Quick tip: ExternalInterface.call is synchronous!

by josefchutka on February 12, 2010

It is a little wonder to me how flash player communicates with JavaScript. Calls are fully synchronous, it works as those (fp and js) were fully integrated into each other. To simulate this lets test some simple flow:

Flash Player calls JavaScript
    JavaScript calls FlashPlayer
        FlashPlayer do some stuff and finishes
    JavaScript finishes
FlashPlayer continues

Following flex application runs simple JavaScript:

document.getElementById("flash").universalCallback();
return "synchronous return";
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    applicationComplete="init()">
<mx:Script>
<![CDATA[
    [Bindable]
    private var lastResult:String = "";

    private function init():void
    {
        ExternalInterface.addCallback("universalCallback", universalCallback);
    }

    private function runScript():void
    {
        var script:String = 'document.getElementById("flash").universalCallback();' +
                'return "synchronous return";';

        var result:String = ExternalInterface.call("function(){" + script + "}");
        lastResult += result + "\n";
    }

    private function universalCallback(... rest):void
    {
        lastResult += "universalCallback" + "\n";
    }
]]>
</mx:Script>
<mx:Button click="runScript()" label="Run"/>
<mx:TextArea width="100%" height="100%" text="{lastResult}" />
</mx:Application>

and results are logged into TextArea:

universalCallback
synchronous return

This means that ExternalInterface.call() is synchronous and waits until called JavaScript finishes. Just after that, flash player moves on the next line. This interesting behaviour makes flash player to hold (but not to freeze) on one line while it is able to continue on different execution (universalCallback()).

Notice, JavaScript to ActionScript data can be sent via ExternalInterface.addCallback() or simple by synchronous return:

var result:String = ExternalInterface.call("function(){" + script + "}");

however, you can not use eval method this way!

var result:String = ExternalInterface.call("eval", script);

Full JavaScript Access From ActionScript (update)

by josefchutka on February 10, 2010

Have you ever tought about accessing DOM from ActionScript? In fact, you can do it and even far more. You can create and call JavaScript methods and objects, access cookies, change styles… All you need is correct AllowScriptAccess parameter within your flash object. No framework needed here, no hacks, ExternalInterface takes care.

Following application runs JavaScript from textarea:

Application. Line 16 does all the fun :-)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    applicationComplete="init()">
<mx:Script>
<![CDATA[
    [Bindable]
    private var lastResult:String = "";

    private function init():void
    {
        ExternalInterface.addCallback("universalCallback", universalCallback);
    }

    private function runScript():void
    {
        ExternalInterface.call("eval", script.text);
    }

    private function universalCallback(... rest):void
    {
        lastResult = String(rest[0]);
    }
]]>
</mx:Script>
<mx:HBox width="100%" height="100%">
    <mx:TextArea id="script" width="100%" height="100%">
        <mx:text>
            <![CDATA[
function myFunction(){
    var flash = document.getElementById("flash");
    var data = document.childNodes[1].innerHTML;
    flash.universalCallback(data);
}

myFunction();
alert(document.getElementsByTagName('title')[0].innerHTML);
            ]]>
        </mx:text>
    </mx:TextArea>
    <mx:TextArea width="100%" height="100%" text="{lastResult}" />
</mx:HBox>

<mx:Button click="runScript()" label="Run"/>
</mx:Application>

HTML template

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
    <title>Full Javascript Access</title>
    <meta http-equiv="Content-Type" content="application/xml; charset=utf-8" />
    <meta http-equiv="Cache-Control" content="no-cache" />
    <meta http-equiv="expires" content="1" />
    <meta http-equiv="pragma" content="no-cache" /> 

    <meta name="author" content="http://studio.yoz.sk" />
    <meta name="description" content="Full Javascript Access" />
    <meta name="robots" content="all" /> 

    <script type="text/javascript" src="js/swfobject.js"></script>
    <script type="text/javascript">
    <!--
        var flashvars = {}

        var params = {
            allowscriptaccess: "always"
        };
        var attributes = {
            id: "flash",
            name: "flash"
        };
        swfobject.embedSWF("Tests.swf", "alternative", "100%", "100%", "10.0.0",
            "flash/expressInstall.swf", flashvars, params, attributes);
    //-->
    </script>

    <style type='text/css'>
    <!--
        body {margin:0px;overflow:hidden;}
        html, body, object, embed {width:100%;height:100%;outline:none;}
    -->
    </style>
</head>
<body>
    <div id="alternative">
        <a href="http://www.adobe.com/go/getflashplayer">
            <img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
        </a>
    </div>
</body>
</html>

No additional nothing here just:

  • line 20: allowscriptaccess: “always” (“sameDomain” may also be used)
  • line 24: tip – add name attribute with swfobject so ExternalInterface.objectID will get correct value

Considerations of using eval:

  • Improper use of eval opens up your code for injection attacks
  • Debugging can be more challenging (no line numbers, etc.)
  • eval’d code executes more slowly (no opportunity to compile/cache eval’d code)

Update (Feb 11, 2010): You can use either eval call:

ExternalInterface.call("eval", script.text);

or wrap your script into anonymous function:

ExternalInterface.call("function(){" + script.text + "}");

Personally, I do not see any difference in execution, it seems to be the same, maybe some performance testing would clarify it. What do you think?

onConference – video chat over facebook

by josefchutka on February 9, 2010

onConference is flash application allowing individuals & businesses to meet face-to-face. Keep in touch with your friends, family and business associates via onConference chat or live video chat.

To be more specific, onConference is using RTMFP protocol, so your conferences, even those multiuser are p2p. Yes, any conference can join multiple users. I decided to use simpliest look-n-feel á la facebook and no pageflow, so you get connected immediately… You can find onConference on http://apps.facebook.com/onconference/, please let me know if you like it, any feedback welcomed :-) .

TextInput wmode opaque/tranpsarent workaround

by josefchutka on February 8, 2010

Many of you have come accross the horrendous bug that happens with text input in flash when the swf is embedded with wmode=transparent/opaque. It has been much discussed (Firefox Bugzilla entry for this bug, Adobe Forum discussion, etc.), but it seems that in all the years that this bug has existed nothing much has been done. A lot of workarounds have been published, many of them based on custom key mapping etc… My workaround uses html element <input type=”text”> that is placed right over flex <mx:TextInput>. Html element is styled transparent (no design) so user will not notice.

Following application uses wmode “opaque”, try inserting characters (čšň) into first text input (mx:TextInput) and than into second (uses html <input type=”text”> over flash)

Main application

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    applicationComplete="init()" viewSourceURL="srcview/index.html">
<mx:Script>
<![CDATA[
    import mx.managers.PopUpManager;
    private function init():void
    {
        var window:FakeInputsWindow = new FakeInputsWindow();
        PopUpManager.addPopUp(window, this);
        PopUpManager.centerPopUp(window);
    }
]]>
</mx:Script>
</mx:Application>

FakeInputsWindow

<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:html="sk.yoz.html.*"
    layout="vertical" width="400" height="250"
    creationComplete="init()"
    move="moveHandler()">
<mx:Script>
<![CDATA[
    [Bindable]
    private var text2:String = "";

    private function init():void
    {
        ExternalInterface.addCallback("fakeTextInputChange", fakeTextChange);
    }

    private function moveHandler():void
    {
        fakeTextInput.positionChanged = true;
        fakeTextInput.invalidateProperties();
    }

    private function fakeTextChange(value:String):void
    {
        text2 = value;
    }
]]>
</mx:Script>
<mx:Text text='&lt;mx:TextInput ....'/>
<mx:TextInput id="text1" width="300" height="20"/>
<mx:Label text="{text1.text}" />
<mx:Spacer height="10" />
<mx:Text text='&lt;input type="text" ....'/>
<mx:Canvas width="300" height="20">
    <mx:TextInput width="300" height="20"/>
    <html:IFrame width="100%" height="100%" id="fakeTextInput"
        autoResize="true"/>
</mx:Canvas>
<mx:Label text="{text2}" />
</mx:TitleWindow>

lines:

  • 15: javascript-to-flash callback definition fakeTextInputChange (from javascript) sends value into actionscript fakeTextChange()
  • 18: fakeTextInput is IFrame class, that controls position and size of html input, lets correct position on TitleWindow move…
  • 35-39: I placed IFrame over TextInput. IFrame is in fact transparent html input, so what user see is just flex TextInput graphics

Html template file

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
    <title>FakeTextInput</title>
    ...
    <script type="text/javascript" src="js/swfobject.js"></script>
    <script type="text/javascript" src="js/flexiframe.js"></script>
    <script type="text/javascript">
    <!--
        var flashvars = {}

        var params = {
            allowscriptaccess: "always",
            wmode: "opaque"
        };
        var attributes = {
            id: "flash"
        };
        swfobject.embedSWF("Tests.swf", "alternative", "100%", "100%", "10.0.0",
            "flash/expressInstall.swf", flashvars, params, attributes);

        function fakeTextInputChange()
        {
            var input = document.getElementById('fakeTextInput');
            var flash = document.getElementById('flash');
            flash.fakeTextInputChange(input.value);
        }
    //-->
    </script> 

    <style type='text/css'>
    <!--
        body {margin:0px;overflow:hidden;}
        html, body, object, embed {width:100%;height:100%;outline:none;}
        #fakeTextInput {
            position:absolute;
            border:none;
            outline:none;
            padding:0;
            margin:0;
            background:transparent url("images/spacer.gif");
            vertical-align:middle;
        }
    -->
    </style>
</head>
<body>
    <div id="alternative">
        <a href="http://www.adobe.com/go/getflashplayer">
            <img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
        </a>
    </div>
    <input type="text" id="fakeTextInput" onkeyup="fakeTextInputChange()"/>
</body>
</html>

Notice lines:

  • 07: import flexiframe.js used for positioning html input on correct place
  • 13: allowscriptaccess must be enabled
  • 14: wmode opaque/transparent is what the source of all evil
  • 22: javascript-to-flash communication
  • 35-42: remove all html input text design and makes it transparent
  • 53: input text definition, you may want to define your own handlers (submit, paste, focus…)