Fresh Thoughts Blog

A quick Flash sound player to use on Facebook and Bebo

Written by John Maver | Jan 30, 2008

For Doorbell on Bebo, I needed an easy way to play an arbitrary doorbell ring. Previously, I had been creating individual .swf files for each ring, and then using the <fb:swf> tag to put them on the page. I was using an AJAX callback to add them when the user clicked a doorbell. The swf was set to auto play one time after load. I had created a separate swf file for each ring using MP3 to SWF Converter. This worked fine, but I didn’t want to have to do this for each new ring. I also wanted the user to be able to click the bell to hear it, and these generated swf files only played once or in a loop, but not on a click. I searched and found FlashDevelop, an open source SWF editor. It is great because it doesn’t require purchasing the Adobe Flash IDE to work, and supports both Actionscript 2 and 3. It is a pretty good editor overall. I hadn’t spent any time with Actionscript before, but it ended up being a lot like Javascript, which I know pretty well. Most of the difficulties came with trying to understand the Flash model, rather than the language. Thankfully, there is Google. The result is a small class that can display an image as the background and can play an .mp3 over the Internet. Using passed in query params, I can control whether the image is shown, which sound to play, and whether it plays right away. Here is how it works: Query params passed in to Flash become global variables off the _root object. I prefaced mine with an “a_”, so I would know which were global variables. You can check them for null to see if they were passed in.

  // Globals passed in by query
  // a_play - if set, the mp3 will play immediately
  // a_ring - the mp3 to play
  // a_nopic - if should hide the background

First, I check to see if I should set the background image by checking for a query parm called “a_nopic”. If it is not set, I load a picture I have added to my swf library.

  if ( _root.a_nopic == null ) {
    _root.attachMovie("library.speaker.jpg", "flashBG", 0);
  }

This says that I should get that image, create a movie based on it, set it as a variable off _root called "flashBG", and put it at the bottom layer(0). Next, I create a new sound object off the _root.

  _root.firstSound = new Sound();

I load the passed in mp3, with the option to play it automatically when it is done by passing "true" as the second parameter. This is controlled by the query param "a_play". Note: the mp3's must have a sample rate of 11KHz, 22KHz, or 44.1KHz or they play too fast.

  _root.firstSound.loadSound(_root.a_ring, _root.a_play != null );

I want the user to be able to click and play the sound, so I set the onRelease function for the _root. This means that when the user releases the mouse button after clicking on the background, the sound will play. I also stop the sound before playing it, so that multiple clicks don't trigger multiple simultaneous sounds.

  _root.onRelease = function() {
    _root.firstSound.stop();
    _root.firstSound.start(0, 1);
  }

The code above is enough to do everything I needed, except for one thing. Unfortunately, the cursor doesn't change to the hand to show the user that the swf is clickable. HTML style tags have no effect, and Flash doesn't natively support changing the cursor easily. There are two ways I found to fix this:

  1. Create a custom cursor. You create a Movie based on a cursor, hide the existing system mouse, and call startDrag() to show your cursor.
      mouse.hide();
      startDrag(_root.yourCursor, true);
  2. Create a giant button that covers the whole swf. The cursor for a button is already the hand cursor.
      _root.rectangle = [Stage.width,Stage.height];
      _root.createEmptyMovieClip("button",300);
      _root.button.lineStyle(1,0x000000,0);
      _root.button.beginFill(0x00000,0);
      _root.button.lineTo(_root.rectangle[0],0);
      _root.button.lineTo(_root.rectangle[0],_root.rectangle[1]);
      _root.button.lineTo(0,_root.rectangle[1]);
      _root.button.lineTo(0,0);
      _root.button.endFill();
    
      // Handle left click
      _root.button.onRelease = function() {
        _root.firstSound.stop();
        _root.firstSound.start(0, 1);
      }

I went with the button option, since I didn't have to create my own cursor for the movie. Here is the whole script:

class Ringer
{
    public static function main()
    {
        // Globals passed in by query
        // a_play - if set, the will play immediately
        // a_ring - the sound to play
        // a_nopic - if should hide the background

        Stage.scaleMode = "noscale";

        // background image
        if ( _root.a_nopic == null ) {
            _root.attachMovie("library.speaker.jpg", "flashBG", 0);
        }

        // sound
        _root.firstSound = new Sound(); 

        // Handle left click
        _root.onRelease = function() {
            _root.firstSound.stop();
            _root.firstSound.start(0, 1);
        }	

        // Load the passed in sound (ex ?a_ring=test.mp3)
        // Sound must be mp3, and have sample rate of 11KHz, 22KHz, or 44.1KHz
        _root.firstSound.loadSound(_root.a_ring, _root.a_play != null );

        // Create a transparent button so that the mouse cursor changes to a hand
        // Make it the size of the whole stage
        _root.rectangle = [Stage.width,Stage.height];
        _root.createEmptyMovieClip("button",300);
        _root.button.lineStyle(1,0x000000,0);
        _root.button.beginFill(0x00000,0);
        _root.button.lineTo(_root.rectangle[0],0);
        _root.button.lineTo(_root.rectangle[0],_root.rectangle[1]);
        _root.button.lineTo(0,_root.rectangle[1]);
        _root.button.lineTo(0,0);
        _root.button.endFill();

        // Handle left click
        _root.button.onRelease = function() {
            _root.firstSound.stop();
            _root.firstSound.start(0, 1);
        }
    }
}

The FBML to call it would be:

  &lt;fb:swf swfsrc='ringer.swf' flashvars='a_ring=your.mp3&amp;a_play=1' imgsrc='yourbg.jpg' width='50' height='50' waitforclick='false' /&gt;