Monday, December 24, 2012

SWFer

Notice:

I have updated SWFer and have made several large changes. The code no longer sucks and it now supports more things. I don't have time to properly update this page currently (it needs a complete rewrite with my changes), so I will leave the original post below. The project is now hosted on Github. The latest version can be downloaded here.

The old original post/page:

SWFer is an application for decompiling and compiling SWF files. It is currently in a very early pre-alpha stage, but I will be making a pre-alpha release with very limited (but hopefully stable) functionality. It was inspired by the complete lack of gaplessly looping MP3 files in SWFs not made with Adobe Flash. I was working on a little game in FlashDevelop and was disappointed that I couldn't get the audio track to properly gaplessly loop. Given my prior experience with FFmpeg, I decided I would find out why and how Adobe Flash created properly looping MP3s and see if I couldn't make a tool to facilitate creating gapless MP3 loops.

The download for the current pre-alpha version of SWFer can be found here. Feedback can be submitted here. (Update: Please don't download this version, as it has been superseded by a new release; see the top of this page for more information)

I cannot stress the pre in pre-alpha release enough. The code is currently horrible, mostly because I've been hacking around at the binary level trying to get the generated SWFs to work as well as they do. I'm releasing this early because a) it's useful, and b) early feedback will help me improve SWFer. The code is going through a massive overhaul, however, even if the end-user doesn't see any noticeable changes.

How to use SWFer: A micro-tutorial

Simply run SWFer, select your source audio file (either a FLAC or WAV), adjust quality parameters (or leave them as they are), give the sound an ActionScript 3 class name, and save the SWF!

Most planned features are disabled in SWFer, and the only available feature is creating SWFs that contain a gaplessly looping MP3. I'll be the first to admit the user interface is not optimal, but for now I'm focusing on features instead of UI, and hopefully it's easy and obvious enough.

How do I use the generated SWF file in my FlashDevelop project?

Let's say you made a SWF file called "MySWF.swf" and inside it you put a sound that you gave the ActionScript 3 class name of "MySound". Loading it is as easy as:

package
{
    import flash.media.Sound;

    [Embed(source = 'MySWF.swf', symbol='MySound')]
    public class MyImportedSound extends Sound
    {
    }
}

Then, to use the imported sound, all you need to do is:

var s:Sound =  new MyImportedSound();
s.play(0, 0); // Feel free to loop using the last parameter of play()

Faux-FAQ

If I already have an MP3 file, can I use SWFer to make a gaplessly looping SWF?
Currently, no. SWFer needs a gapless audio file as a source input (currently I only allow WAV and FLAC) in order to generate a gapless SWF+MP3. MP3s always have an intro and outro gap (technically, it's possible to make a gapless MP3, but it's complex and no encoder+decoder that I know of does), so if you are using an already-existing MP3, SWFer can't do necessary pre-processing to determine how to skip the gaps. Someday, I'd like to add some automatic heuristic guessing to remove gaps from already existing MP3s and allow users to manually tweak settings.
What platforms are supported?
Currently, only Windows. The code is cross-platform, however, and I plan to release OS X and Linux builds (someday).
Does SWFer reduce the quality of my audio?
Not any more than encoding to an MP3 does. SWFer uses the FFmpeg project libraries and LAME, which is one of, if not the, highest quality MP3 encoders available.
How does SWFer gaplessly loop MP3s?
If you're tech-savvy and open a SWF file in a hex editor or some program, you can find two tags: a DefineSound tag, which actually defines a sound and contains its sample data, and the StartSound (or StartSound2) tag, which starts playing a sound and declares how many beginning samples to skip. If you know the number of samples for the starting and ending gaps in your MP3, you can set the StartSound tag to skip the right number of samples for the starting gap, and then set the right number of samples to play in the DefineSound tag so that it stops playing before the end gap starts.
What is SWFer's license?
SWFer is licensed under the GNU GPL. See its corresponding license file for more information. I may consider changing the license in the future, but I make no promises. Don't worry, your generated SWF files are yours to keep and don't fall under the GPL.
Where can I learn more about the SWF file format?
SWF File Format Specification Version 10
Where can I learn more about ActionScript 3 byte code?
ActionScript Virtual Machine 2 (AVM2) Overview
Why is it "Faux-FAQ" and not just "FAQ?"
Because I've never been asked any questions about this, so these aren't really frequently asked questions. They're just ones I assume are useful to know.

Monday, December 10, 2012

Determining if your Android Application is in Background or Foreground

This question comes up a lot. How can you tell if your application is going into the background, or if you're just switching between activities in your own application? The "right" solution is both intrusive and, I might say, outdated. getRunningTasks() is certainly wrong (see the same question as for why). So what's the real right way?

ActivityLifecycleCallbacks

The answer is the poorly documented Application.ActivityLifecycleCallbacks, added in API level 14 (Android 4.0 Ice Cream Sandwich). Granted, this method doesn't work if you're targeting something earlier than API level 14, but hopefully we can encourage people to develop for later versions of Android and encourage phone manufacturers to update their phones.

How it works

The key is in understanding how activities coordinate with each other. When switching between activities A and B, their methods are called in this order:

A.onPause();
B.onCreate();
B.onStart();
B.onResume(); (Activity B now has user focus)
A.onStop(); (if Activity A is no longer visible on screen)

I wrote about the callback calling order between the ActivityLifecycleCallbacks and Activity classes earlier, and the key is checking if the number of resumed activities equals the number of stopped activities. If so, then your application is being backgrounded. If more activities have been resumed then stopped, then your application is in the foreground. Check it out:

// File MyLifecycleHandler.java
public class MyLifecycleHandler implements ActivityLifecycleCallbacks {
    // I use two separate variables here. You can, of course, just use one and
    // increment/decrement it instead of using two and incrementing both.
    private int resumed;
    private int stopped;
    
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    public void onActivityDestroyed(Activity activity) {
    }
    
    public void onActivityResumed(Activity activity) {
        ++resumed;
    }
    
    public void onActivityPaused(Activity activity) {
    }

    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    public void onActivityStarted(Activity activity) {
    }

    public void onActivityStopped(Activity activity) {
        ++stopped;
        android.util.Log.w("test", "application is being backgrounded: " + (resumed == stopped));
    }
    
    // If you want a static function you can use to check if your application is
    // foreground/background, you can use the following:
    /*
    // Replace the two variables above with these two
    private static int resumed;
    private static int stopped;
    
    // And add this public static function
    public static boolean isApplicationInForeground() {
        return resumed > stopped;
    }
    */
}



// File MyApplication.java
// Don't forget to add it to your manifest by doing
// <application android:name="your.package.MyApplication" ...
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        // Simply add the handler, and that's it! No need to add any code
        // to every activity. Everything is contained in MyLifecycleHandler
        // with just a few lines of code. Now *that's* nice.
        registerActivityLifecycleCallbacks(new MyLifecycleHandler());
    }
}

I posted this method on StackOverflow and someone asked about a special detail of the onStop() method of activities. The docs for onStop() say:

Note that this method may never be called, in low memory situations where the system does not have enough memory to keep your activity's process running after its onPause() method is called.

Since this method depends on onStop()/onResume(), you might be worried that if onStop() isn't called (because of low memory), that this method of checking for backgrounding will be invalid. This, however, is not the case. Note specifically that the docs say "your activity's process". If you're in a low memory situation, your process is actually killed (not just your activity). This method remains valid because a) if your process is killed, you can't check for backgrounding anyway, and b) if your process starts again (because one of your activities is being created again) the counter variables will be reset to zero.

Two Caveats

#1: You need to handle your own configuration changes (that is, specify android:configChanges="keyboardHidden|orientation" in your Android.xml file). If you don't, and you rotate your device, for example, then your activity will be stopped and fully destroyed before it is restarted and resumed. If you want more details on exactly why this is problematic, I talk about it in my SO answer.

#2: Note that if you use this method, you shouldn't check if your application is being backgrounded in your Activity's onPause(). This is the wrong place for checking for backgrounding. You should check for backgrounding in your Activity's onStop() method, after you call super.onStop().

As for why this is the case: onPause() is called while your activity is still in the foreground, so checking if it's in the background or foreground doesn't really make sense. If you're checking for backgrounding in onPause() it's like trying to know the future before it happens. Android doesn't give you information on what's going to happen next (because Android says it's none of your business), so you must keep track of this information yourself. You could probably work this out, but it would require you to carefully track all your started activities and Intents, plus whenever an Activity finish()es. In other words: it gets complicated. Just design your app so that you check for backgrounding in onStop().

ActivityLifecycleCallbacks and Activity: Callback calling order

The documentation on ActivityLifecycleCallbacks is rather sparse, at best. It seems easy enough to use, but it's not obvious when the ActivityLifecycleCallbacks's functions are called in relation to the Activity's functions. I did a little test:


ActivityLifecycleCallbacks:
// ...
public void onActivityResumed(Activity activity) {
    android.util.Log.w("test", "ActivityLifecycleCallbacks.onActivityResumed()");
}

public void onActivityPaused(Activity activity) {
    android.util.Log.w("test", "ActivityLifecycleCallbacks.onActivityPaused()");
}
// ...

Activity:
// ...
@Override
protected void onResume() {
    android.util.Log.w("test", "Activity.onResumed() (before super)");
    super.onResume();
    android.util.Log.w("test", "Activity.onResumed() (after super)");
}
    
@Override
protected void onPause() {
    android.util.Log.w("test", "Activity.onPaused() (before super)");
    super.onPause();
    android.util.Log.w("test", "Activity.onPaused() (after super)");
}
// ...

And the output:
12-10 13:58:35.699: W/test(9794): Activity.onResumed() (before super)
12-10 13:58:35.699: W/test(9794): ActivityLifecycleCallbacks.onActivityResumed()
12-10 13:58:35.709: W/test(9794): Activity.onResumed() (after super)
12-10 13:58:35.749: W/test(9794): Activity.onPaused() (before super)
12-10 13:58:35.749: W/test(9794): ActivityLifecycleCallbacks.onActivityPaused()
12-10 13:58:35.749: W/test(9794): Activity.onPaused() (after super)

Now for the curious, it looks like ActivityLifecycleCallbacks's functions are called as part of the super call chain. So you can expect ActivityLifecycleCallbacks's callbacks to have fired by the time the Activity's super calls finish. Good to know!