Posts in Category "ActionScript"

Front-facing Camera Support in AIR 3

Adobe AIR for mobile has had consistent camera support since AIR 2.6, and now in AIR 3 (in beta, available on Adobe Labs), we’ve introduced support for front-facing cameras, as well. The introduction of the new position property on Camera along with the constants in the new CameraPosition class (BACK, FRONT, and UNKNOWN) allow you to chose which camera you want to get a reference to before attaching it to a Video object.

To be honest, the API is not quite as elegant as I’d like because making it consistent across platforms (mobile and desktop) meant having to compromise a little intuitiveness, but it’s easy enough to figure out, and most importantly, it’s entirely cross-platform. The code below shows a simple function that will return the requested camera:

// Get the requested camera. If it cannot be found,
// return the device's default camera instead.
private function getCamera(position:String):Camera
{
  for (var i:uint = 0; i < Camera.names.length; ++i)
  {
    var cam:Camera = Camera.getCamera(String(i));
    if (cam.position == position) return cam;
  }
  return Camera.getCamera();
}

For a full working example, check out the demo application called FrontCameraExample on Github (you can also download the FXP file). Keep in mind that AIR 3 is still in beta, so you might find bugs. If you do, here’s how to file them.

How to Tell Which Flex Components Have Been Mobile Optimized

Even when you work for Adobe, it’s hard to keep up with everything we’re doing — especially when it comes to AIR, Flex, and Flash Builder. I started building a new Flex Mobile application yesterday, and I realized that I didn’t know exactly which Flex components had been mobile optimized to-date. Rather than just asking someone for a list, I asked around to find out the best way to keep updated.

Piotr Walczyszyn gave me the excellent suggestion of checking the documentation for mobile skins. If a component has a mobile skin, it has been mobile optimized, and since the docs are kept up to date, this seems like as good a way as any to know which components are safe to use in a mobile app and which to avoid.

Ultimately, I would like to see our docs allow filtering by mobile optimization, but until that happens, this will work almost as well.

When AIR Applications Prevent Shutdown or Restart on Mac OS X

Update (7/27/2011): I just added another partial solution at the end of this post.

My friend Ben Simon occasionally uses AIR when he has clients who need cross-platform desktop applications. Recently, Ben discovered that an AIR application he’s working on is preventing OS X from shutting down or restarting. Specifically, when the application is running, choosing "Restart…" or "Shut Down…" from the Apple menu results is the OS claiming that the AIR application canceled the restart.

I started looking into the issue for him, and the first thing I discovered is that this definitely doesn’t happen with all AIR applications. I was able to narrow the issue down pretty quickly to just applications that stay running even though all their windows are closed. Further investigation led me to discover that the issue has to do with calling event.preventDefault() on the closing event thrown by NativeWindow (one technique for having applications "minimize" to the system tray on Windows or the Dock on Mac is stop the main application window from closing and hiding it, instead).

From what I can tell, there are two pretty solid ways to work around this issue:

  1. Use custom chrome. If you draw your own window chrome, rather than listening for the CLOSING event and calling event.preventDefault(), you can just listen for the click event on the close button in the window chrome and set the visible property on the window to false right there.
  2. If you really want or need to use system chrome, the best work-around I’ve come up with so far is to move your main application into a separate component that extends NativeWindow (let’s call it Main.mxml). That way, you can set the visible property of your main application window to false, then just open and close new instances of Main.mxml as you need to (as opposed to toggling the visible property).

There are probably other ways to get this to work, but unfortunately I’ve run out of time to test them right now. If you have your own techniques, feel free to post them in the comments.

Update (7/27/2011): A partial solution occurred to me this morning. If you check to see if your window is visible before calling preventDefault(), your application won’t stop the OS shutdown in cases where all your application windows are already closed. The following code will do the trick:

private function onWindowClosing(e:Event):void
{
    if (this.visible)
    {
        e.preventDefault();
        this.visible = false;
    }
}

The reason this is only a partial solution is that it only stops preventDefault() from being called when the window isn’t visible, but if your application has visible windows, preventDefault() will still be called which will still cancel the OS shutdown or restart. Better than nothing, but still not perfect.

Tooling for Adobe AIR 2.6

Since we launched AIR 2.6, I’ve seen a lot of questions online about tooling support, so I thought I’d try to clear some things up.

We frequently release the AIR runtime and SDK in advance of releasing new versions of tools (Flash Builder and Flash Professional) for two reasons:

  1. The tools need some additional time to catch up to the SDK. In other words, the tools are downstream of the SDK, so after we’ve added all our features, the Flash Builder and Flash Professional teams need time to add and test support for those features.
  2. Although we could hold off on releasing the SDK until the tools were ready, we figure it’s better to put the runtime and the SDK into developers’ hands as early as possible since, even without tooling support, it is still possible to develop with them (primarily from the command line).

The next versions of Flash Builder and Flash Professional will support AIR 2.6, and will be out the first half of this year. In the meantime, I would encourage Flash Builder users to join the private pre-release program which gives you access to a build of Flash Builder which has support for AIR 2.6. There is currently no pre-release program offered for Flash Professional, although Flash Professional users can still use the AIR 2.6 SDK from the command line.

Although it’s not officially supported, Flash Builder users can also use either Flash Builder 4 or Flash Builder “Burrito” to build AIR 2.6 applications, but with two caveats:

  1. The mobile workflow in Burrito doesn’t work with the 2.6 SDK. You can build 2.6 applications (desktop or mobile), but Burrito won’t automatically deploy them to a device. You will need to use the 2.5 SDK to get device integration, or deploy your application to your device manually (I use the adb tool that comes with the Android SDK).
  2. If you use Flash Builder 4 for AIR 2.6 development, make sure to read the post How to Use AIR 2.6 with Flash Builder 4. (The quick answer is to add the -target-player=11 flag to the compiler arguments.)

We know it’s not ideal to release a new runtime and SDK without also releasing tooling support at the same time, but we feel like getting a new version of AIR out there early is worth the tradeoffs.

Owned Windows in AIR 2.6

One of the new desktop features in AIR 2.6 is owned windows. Owned windows are primarily designed for when you want a utility or a lightweight window to be related to another window as in the case of a tool pallet. The following describes the relationship between owned windows and their owners:

  • Owned windows always have a higher z-order than their owners which means they always appear on top (you wouldn’t want a tool pallet to get lost behind your application window).
  • When you minimize or hide the owner, all owned windows are minimized, as well. When you restore the owner, the owned windows are also restored.
  • When you close the owner, all owned windows also close.

Here’s an example of owned windows in action:


And here’s the code:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx">
    <fx:Script>
        <![CDATA[
            private function openNewWindow(owned:Boolean):void
            {
                var initOptions:NativeWindowInitOptions = new NativeWindowInitOptions();
                initOptions.type = NativeWindowType.UTILITY;
                if (owned) initOptions.owner = this.nativeWindow;
                var newWin:NativeWindow = new NativeWindow(initOptions);
                newWin.title = (owned) ? "Owned Window" : "Normal Window";
                newWin.activate();
            }
        ]]>
    </fx:Script>
    <s:VGroup width="100%" height="100%" verticalAlign="middle" horizontalAlign="center">
        <s:Button label="Open Ownded Window" click="openNewWindow(true);"/>
        <s:Button label="Open Regular Window" click="openNewWindow(false);"/>
    </s:VGroup>
</s:WindowedApplication>

Native Cursors in AIR 2.6

One of the coolest new desktop features in AIR 2.6 is the ability to use native cursors. By "native cursor," I mean custom cursors at the OS level as opposed to the runtime level. In other words, rather than hiding the mouse cursor and rendering a sprite in its place (the old way to do custom cursors which doesn’t always perform well), in AIR 2.6, you can hand one or more bitmaps to the operating system along with a frame rate and a hot spot, and the cursor will be rendered (and optionally animated) in hardware.

Here’s an example:


Here’s everything you need to know about custom cursors in AIR 2.6:

  • The data property of MouseCursorData takes a vector of BitmapData objects so you can create an animation. Use the frameRate property to control the animation’s frame rate.
  • You can control which part of the cursor is the "hot spot" (the portion that registers clicks) using the hotSpot property of MouseCursorData.
  • When the cursor leaves the application’s native window, your custom cursor will revert to the default OS cursor.
  • The maximum cursor size is 32×32.

Full docs are available here.

And here’s the code. (I used the Smurf sprite sheet from here, though I put them all on a single row and scaled them down to 32×32 to simplify the code.)

package
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.ui.Mouse;
    import flash.ui.MouseCursorData;

    [SWF(backgroundColor="0x000000")]
    public class NativeCursorExample extends Sprite
    {
        [Embed(source="smurf.png")] private var SmurfClass:Class;

        private static const SMURF_WIDTH:uint  = 32;
        private static const SMURF_HEIGHT:uint = 32;

        private var SMURF_CURSOR_NAME:String = "smurf";

        public function NativeCursorExample()
        {
            var bitmaps:Vector.<BitmapData> = new Vector.<BitmapData>;

            var spriteSheet:Bitmap = new SmurfClass();

            var r:Rectangle;
            var bmd:BitmapData;
            var p:Point = new Point(0, 0);

            for (var i:uint = 0; i < 16; ++i)
            {
                r = new Rectangle((SMURF_WIDTH * i) , 0, SMURF_WIDTH, SMURF_HEIGHT); 
                bmd = new BitmapData(r.width, r.height, true, 0x000000);
                bmd.copyPixels(spriteSheet.bitmapData, r, p);
                bitmaps.push(bmd);
            }
            var mcd:MouseCursorData = new MouseCursorData();
            mcd.data = bitmaps;
            mcd.hotSpot = new Point(22, 15); // The smurf's nose
            mcd.frameRate = 24;
            Mouse.registerCursor(SMURF_CURSOR_NAME, mcd);
            Mouse.cursor = SMURF_CURSOR_NAME;
        }
    }
}

How to Use AIR 2.6 with Flash Builder 4

Flash Builder 4 and AIR 2.6 are technically not a supported configuration, and the version of Flash Builder Burrito that is on Labs is not 2.6 compatible yet, either. Fortunately, with a very simple work-around, it is possible to combine AIR 2.6 and Flash Builder 4.

The first thing you need to do is overlay the SDK (combine the AIR SDK with the Flex SDK). These instructions haven’t been updated in some time, but they’re still accurate.

Once you’ve added the new SDK in Flash Builder, you will find that you have access to new AIR 2.6 APIs (overview here), however if you try to run your application, you will get a runtime VerifyError. To work around this issue, follow these simple steps:

  1. Right-click on your project and choose "Properties".
  2. Select the "Flex Compiler" section.
  3. In the "Additional compiler arguments" field, add -target-player=11.
  4. Click "OK".

Debug your application again, and everything should work fine.

How to Create an ActionScript AIR Project in Flash Builder

In Flash Builder 4, I used to follow these steps to create an ActionScript AIR project:

  1. Go to File > New > Flex Project.
  2. Create a project as usual, making sure “Desktop” is chosen as the Application Type.
  3. Rename the default application MXML file to something temporary.
  4. Create a new ActionScript file with the name of the application (and make sure it extends Sprite).
  5. Right click on the new ActionScript file, and choose “Set as Default Application.” This renames the application descriptor file accordingly.
  6. Delete the old temporary MXML file.

Not very smooth. Fortunately, in Flash Builder “Burrito” (still a preview release), there’s an ActionScript Desktop (AIR) project. To create an ActionScript AIR project in “Burrito,” all you have to do is:

  1. Select File > New > ActionScript Project.
  2. Set up your project as usual and make sure the “Application Type” is set to “Desktop.”

This is obviously a much better work-flow, and a big improvement. There’s even mobile application types for both Flex and ActionScript. If you haven’t tried “Burrito” yet, give it a try.

How to Use CameraUI in a Cross-platform Way

If you use, or plan to use, the CameraUI API, this post will explain how to do it in a cross-platform way so your code will work on both iOS and Android.

The AIR mobile profile supports an API called CameraUI which can be used to launch a device’s native camera application for taking both still photos and video. AIR applications can then access the photo or video and do whatever they want with it. I prefer this method to the Camera ActionScript API because it gives the user the native camera experience he or she is accustomed to along with all the camera features and functionality available on the device.

The problem with the API is that there’s an easy way to use it, and a slighter harder way, and the easy way is not cross-platform. I discovered this when writing AIRBench — an AIR application that demonstrates and tests all the mobile profile specific APIs in AIR. I wrote AIRBench before the CameraUI APIs were available on iOS, so I was only able to test them on Android. Naturally, being a programmer who prefers simplicity to complexity, I used the easy CameraUI APIs only to discover, when running AIRBench on iOS, that the CameraUI test didn’t work.

The easy (and non cross-platform) way to use the CameraUI API is to do something like this:

private function onLaunchCameraUI(e:Event):void
{
    var cameraUI:CameraUI = new CameraUI();
    cameraUI.addEventListener(MediaEvent.COMPLETE, onCameraUIComplete);
    cameraUI.launch(MediaType.IMAGE);
}

private function onCameraUIComplete(e:MediaEvent):void
{
    var picture:File = e.data.file;
}

The data property of the MediaEvent is a MediaPromise object which has a file property which, in some cases, points to the photograph that was just taken. In some cases, but not all. On Android, when the CameraUI is used to take a picture, the photo is stored in the camera roll so the file property of the MediaPromise can reference it. On iOS, however, the image isn’t stored in the camera roll (unless you choose to do so yourself) which means that the file reference is null.

The better (more cross-platform) way to write this code is to load the image asynchronously from the MediaPromise itself. Here’s the refactored code from AIRBench:

private function onLaunchCameraUI(e:Event):void
{
    var cameraUI:CameraUI = new CameraUI();
    cameraUI.addEventListener(MediaEvent.COMPLETE, onCameraUIComplete);
    cameraUI.addEventListener(Event.CANCEL, onCameraUICanceled);
    cameraUI.addEventListener(ErrorEvent.ERROR, onCameraError);
    cameraUI.launch(MediaType.IMAGE);
}

private function onCameraUIComplete(e:MediaEvent):void
{
    var cameraUI:CameraUI = e.target as CameraUI;
    cameraUI.removeEventListener(MediaEvent.COMPLETE, onCameraUIComplete);
    cameraUI.removeEventListener(Event.CANCEL, onCameraUICanceled);
    cameraUI.removeEventListener(ErrorEvent.ERROR, onCameraError);

    var mediaPromise:MediaPromise = e.data;
  
    this.mpLoader = new Loader();
    this.mpLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onMediaPromiseLoaded);
    this.mpLoader.addEventListener(IOErrorEvent.IO_ERROR, onMediaPromiseLoadError);
    this.mpLoader.loadFilePromise(mediaPromise);
}
  
private function onMediaPromiseLoaded(e:Event):void
{
    var mpLoaderInfo:LoaderInfo = e.target as LoaderInfo;
    mpLoaderInfo.removeEventListener(Event.COMPLETE, onMediaPromiseLoaded);
    mpLoaderInfo.loader.removeEventListener(IOErrorEvent.IO_ERROR, onMediaPromiseLoadError);
    this.imageDisplay.source = mpLoaderInfo.loader;
}

It’s a few more lines of code, but it works perfectly on Android and on iOS, and it will also work on any mobile profile platforms that AIR supports in the future.

If you’re using the CameraUI API now on Android, I strongly encourage you to use the technique described above to ensure that your code is as portable and cross-platform as possible.

How to Discover Your AIR Runtime Version

This morning, someone asked me how to figure out what version of the SDK and/or AIR runtime you’re using. Rather than just respond to the email, I figured I would blog it for everyone to see.

Here are two easy ways to see the AIR runtime version you’re using:

  1. Check the "AIR SDK Readme.txt" file in your SDK directory (the version is at the top).
  2. Trace out NativeApplication.nativeApplication.runtimeVersion.

Remember that you don’t want to check the runtime version to see if certain capabilities, features, or APIs exist — rather, you want to use an individual feature’s’ "isSupported" flag. But for debugging purposes, knowing your runtime version programmatically can sometimes be useful.