Opening Links in Flash

Opening links to web pages from AS3 is pretty well covered in various online documentation.  It’s similarly straightforward in Haxe.  Recently, however, I came to realize that it wasn’t normal Flash behavior that Haxe programs would only open links in the browser or standalone player if served from a server,  even localhost, and not from a local ‘file:’ URL.

It turned out this was because Haxe by default compiles to a different, more restricted network security model than that to which the Adobe tools compile.  Even if opened from a MouseEvent, the navigation calls are blocked, as is all network access from a local SWF.  To enable this network access and allow links to be opened, you have to compile with the network-sandbox compiler flag (-Dnetwork-sandbox).  The documentation for it is a little non-intuitively ambiguous, but that actually enables more access than the default.  Links should now all work, from a hosted or local SWF.

As a sidenote, the code to open a link in Haxe looks like this:

import flash.Lib;
import flash.text.TextField;
import flash.display.Sprite;
import flash.net.URLRequest;
import flash.events.MouseEvent;
import flash.events.Event;

class Sample extends Sprite
{
   public function new()
   {
      super();

      Lib.current.stage.addChild(this);

      var label:TextField=new TextField();
      label.width=800;
      label.text="Click to open google!";
      addChild(label);
      Lib.current.stage.addEventListener(MouseEvent.CLICK,onClick);
   }

   public function onClick(inEvent:MouseEvent)
   {
     Lib.getURL(new URLRequest("http://www.google.com"));
   }

   public static function main()
   {
     new Sample();
   }
}

Its equivalent in AS3 looks like the following:

package {

  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.net.URLRequest;
  import flash.events.MouseEvent;
  import flash.events.Event;

  import flash.net.navigateToURL;
  import flash.net.URLRequest;

 public class LinkTest extends Sprite {
    public function LinkTest() {
      var label:TextField = new TextField();
      label.width = 200;
      label.text = "Click here";
      addChild(label);


      label.addEventListener(MouseEvent.CLICK, onClick);
    }


    public function onClick(e:MouseEvent) {
      navigateToURL(new URLRequest("http://google.com"));
    }
  }
}

Internally Haxe’s flash.Lib is actually literally just calling navigateToURL via a pretty wild looking cast on the global symbol table entry, since Haxe has no concept of package level functions like AS3 does.

Play External SWF MovieClip from Haxe/NME

To tease with some exciting news, Gold Leader has been picked up for a sponsorship!

On an immediate coding topic though, this has meant dealing with something I’d been dreading: Incorporating the sponsor’s splash screen.  As expected they gave me an FLA and compiled SWF.  Fortunately it turned out to be just a simple movie, no buttons to hook up or anything.  Quite typically for Haxe/NME/ActionScript, making this work turned out to be not muchcode, but kind of a pain to figure out.

NME, along with the SWF library, does now include some cross-platform support for playing SWFs.  Josh Granick has notes here, although be sure to read the comments as the API has been updated since that post (I don’t see a particularly better update post).  However, as far as I can tell, at the moment this only supports the graphics.  It wouldn’t play the sound effect associated with my movie and threw an error trying to decode it (something like “Unknown sub-tag SoundStreamHead2,” where the latter begins the encoded sound chunk in the SWF).

I really only care about playing the SWF in Flash though, so happily instead I can just have Flash load and play the movie.  First off, the movie gets included in the compiled program SWF as a binary asset.  Simply copy the movie SWF into your assets folder, include with the assets command in your .nmml as usual, and NME will assume it’s a binary asset.

The code to then play that movie clip looks something like this:

import nme.display.Sprite;

import nme.Assets;
import nme.Lib;

import nme.display.Loader;
import nme.display.MovieClip;
import nme.events.Event;

class Main extends Sprite {

public function new () {

  super ();

  var bytes = Assets.getBytes("assets/splash.swf");
  trace("Bytes " + bytes.length);

  var loader:Loader = new Loader();
  loader.loadBytes(bytes);

  loader.contentLoaderInfo.addEventListener
    (Event.COMPLETE,
     function (_) {
       var mc:MovieClip = cast(loader.content, MovieClip);
       trace("Frames: " + mc.totalFrames);
       mc.addFrameScript(mc.totalFrames-1,
                         function():Void {
                           trace("Done.");
                           mc.stop();
                           removeChild(mc);
                         });
       addChild(mc);
     });

    // end main
  }

  // end Main
}

Note that you could skip the dynamic cast to MovieClip and simply add the loader object to the stage/parent directly. However, I need the movie object itself so I can detect when it has stopped and move on to the actual game.

That last point is itself somewhat interesting. Utterly shockingly, ActionScript3 has an event for absolutely everything except movie clips completing. Seemingly it’s just not there, doesn’t exist… This blew my mind.

What most people do is add a function to the ENTER_FRAME event that checks every frame to see whether or not the current frame is the last frame, and throw another event or set a property if so.  However, Flash internally can associate a script with each frame, to be executed as it plays.  The seemingly undocumented function MovieClip.addFrameScript() allows you to add a function to particular frames of the movie.  In general you’d have to be careful about blowing away other code, but here I don’t have that problem—there’s nothing there, and I just want to bail.  So, I add a function to the last frame of the movie to do some housekeeping, and we’re all set!

Like I said, this is all fairly simple in the code, but I haven’t seen anybody spell out how to make this work, so hopefully this will be of use.

Haxe/NME Android on Arch

These are some notes, the tricky parts mostly from tom5760, on building Haxe/NME apps for Android.

First install Ant and Java using the standard tools. Despite various warnings around, I have not had a problem using Java7 (OpenJDK).  Then install the Android tools using the AUR packages android-sdk android-sdk-platform-tools android-ndk. I use packer, so it’s as simple as:

$ sudo packer -S android-sdk \
                 android-sdk-platform-tools android-ndk

After that you need to use the android manager to install support for your API and device targets, like usual.

Next configure NME with the right paths by running nme setup android. Note that you need to do this as your user, not sudo. Opt not to download any of the packages. If you did a standard install you should have JAVA_HOME and ANT_HOME set for it to detect, but these are the standard paths:

  • SDK: /opt/android-sdk
  • NDK: /opt/android-ndk
  • Ant: /usr/share/apache-ant
  • Java: /usr/lib/jvm/java-7-openjdk

After that, projects still won’t compile. You’ll probably get errors like this:

Creating hxcpp.h.gch...
[ huge compile command... ]
/usr/lib/haxe/lib/hxcpp/2,10,2//include/hxcpp.h:13:20: error: typeinfo: No such file or directory
In file included from /usr/lib/haxe/lib/hxcpp/2,10,2//include/hxcpp.h:170: /usr/lib/haxe/lib/hxcpp/2,10,2//include/Array.h:195:21: error: algorithm: No such file or directory
In file included from /usr/lib/haxe/lib/hxcpp/2,10,2//include/hxcpp.h:162: /usr/lib/haxe/lib/hxcpp/2,10,2//include/hx/Object.h: In member function 'void hx::ObjectPtr::CastPtr(hx::Object*)': /usr/lib/haxe/lib/hxcpp/2,10,2//include/hx/Object.h:143: error: must #include before using typeid
In file included from /usr/lib/haxe/lib/hxcpp/2,10,2//include/hxcpp.h:170: /usr/lib/haxe/lib/hxcpp/2,10,2//include/Array.h: In member function 'void Array_obj::sort(Dynamic)': /usr/lib/haxe/lib/hxcpp/2,10,2//include/Array.h:388: error: 'sort' is not a member of 'std'
In file included from /usr/lib/haxe/lib/hxcpp/2,10,2//include/hxcpp.h:171: /usr/lib/haxe/lib/hxcpp/2,10,2//include/Class.h: In function 'bool hx::TCanCast(hx::Object*)': /usr/lib/haxe/lib/hxcpp/2,10,2//include/Class.h:139: error: must #include before using typeid
Called from ? line 1
Called from BuildTool.hx line 1301
Called from BuildTool.hx line 567
Called from BuildTool.hx line 604
Called from BuildTool.hx line 738
Called from BuildTool.hx line 767
Called from BuildTool.hx line 162
Uncaught exception - Error creating pch: 256 - build cancelled
Error: Source path "bin/android/obj/libApplicationMain.so" does not exist

These are caused by Haxe/NME not locating the NDK libraries correctly. Recent versions of Android reorganized the directory structure a bit, so you have to do this:

cd $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++
ln -s 4.4.3/libs
ln -s 4.4.3/include

Apparently version 4.6 (the latest as of this writing) doesn’t work.

At that point, everything should work, but I got an error like this:

Copy bin/android/obj/libApplicationMain.so to bin/android/bin/libs/armeabi/libApplicationMain.so
cd bin/android/bin
/SDKs//ant/bin/ant debug
sh: /SDKs//ant/bin/ant: No such file or directory
Called from ? line 1
Called from InstallTool.hx line 679
Called from InstallTool.hx line 119
Called from installers/InstallerBase.hx line 229
Called from installers/AndroidInstaller.hx line 56
Called from helpers/AndroidHelper.hx line 53
Called from helpers/ProcessHelper.hx line 125
Called from InstallTool.hx line 152
Called from /usr/lib/haxe/std/neko/Lib.hx line 63
Called from helpers/ProcessHelper.hx line 119
Called from helpers/ProcessHelper.hx line 169
Uncaught exception - Error running: /SDKs//ant/bin/ant debug [bin/android/bin]

To fix that, I edited ~/.hxcpp_config.xml to set the various paths:

<section id="vars">
<set name="SDK_ROOT" value="/SDKs/" />
<set name="ANDROID_SDK" value="/opt/android-sdk" />
<set name="ANDROID_SETUP" value="true" />
<set name="ANDROID_NDK_ROOT" value="/opt/android-ndk/" />
<set name="ANT_HOME" value="/usr/share/apache-ant" />
<set name="JAVA_HOME" value="/usr/lib/jvm/java-7-openjdk" />
</section>

I have not done more yet to figure out why I needed to do that.

Note also that you need to include the following NDLLs in your .nmml to compile for Android, though they don’t seem to be needed for other platforms:

<ndll name="std" />
<ndll name="regexp" />
<ndll name="zlib" />
<ndll name="nme" haxelib="nme" />

The latter seems to be needed even if including the nme haxelib. Again, I have not investigated farther.

The Android target also does not like the portrait="*" option in the nmml file, you must specify landscape or portrait.

At this point, you should be able to build and run Android apps, e.g.:

/usr/lib/haxe/lib/nme/3,4,4/samples/02-Text/
nme test Sample.nmml android

Note that the above will only work if you can write to the samples dir, e.g., chown -R joe /usr/lib/haxe/lib/nme/3,4,4/samples/.