Friday, July 1, 2011

Since jQuery 1.5, Deferred was introduced in the framework and the whole Ajax module was rewritten with it. Deferred is implemented based on the Promise/A design. I had heard of this concept before but never paid much attention to it until jQuery, which I use a lot of, implemented it. I realized the power of it, but never got a chance to use it until now.

Now I am faced with a problem (probably a design flaw from the beginning). I am using a jQuery plugin called jPlayer, which is a great library for playing sound on web pages. In order to simply the use of jPlayer, I decided to write a method that will play sound for a given sound URL path, so that everyone using it will just have to give the path of the sound file to play and not have to worry about anything else. So, on every page that uses jPlayer, I instantiate/setup an instance of jPlayer so it can be reused (only one sound will be played at once) for every sound file.
Code looks like:

The method playMusic is exposed for use.
({"solution" : "flash, html"} is there to make sure FF uses Flash. In most IE, it will use Flash automatically.)

Notice the "ready" parameter in jPlayer's constructor. It is a callback for the constructor. The reason for it is because in some browser Flash is required to play sound file, and the loading time of Flash is unknown and asynchronous. The callback is to tell you when the Flash is ready then you can play your sound. If is not ready and you play, nothing will happen and you're left in the dark to wonder why.
This worked fine for most of the cases, since, up and until I see my problem, all my use cases were that user will click something to trigger the play. And by that time, Flash is most likely to be loaded. Yes, it's a dangerous assumption.

Now the problem comes when I need to play sound when the page finished loading. If I simply add a line like this

window.onload = function(){
playMusic("../images/applause.mp3");
}

or simply

playMusic("../images/applause.mp3");

These don't work.
For the first case, Flash is not necessarily ready when playMusic is called.
The second case has the following output from Firebug and obvious don't work:

play music: ../images/applause.mp3

player ready

Now, without changing any API, I need a way to get my function or sound played when the Flash is ready. Here is where Deferred comes into play.
I simply has to instantiate an instance of Deferred and let my playMusic passes every call to the Deferred.
And resolves the Deferred when Flash is ready. This way all calls to playMusic will queued up in the Deferred before it is resolved, and if it is resolved, the function will be called immediately. Also, this way I don't have to worry about when and where I am calling playMusic.
Here is the new code: