Simplifying sound loading and playback using the SoundFacade class

The ActionScript 3.0 sound architecture is powerful but complex. Applications that only need basic sound loading and playback features can use a class that hides some of the complexity by providing a simpler set of method calls and events. In the world of software design patterns, such a class is called a facade.

The SoundFacade class presents a single interface for performing the following tasks:

The SoundFacade class tries to offer most of the functionality of the ActionScript sound classes with less complexity.

The following code shows the class declaration, the class properties, and the SoundFacade() constructor method:

public class SoundFacade extends EventDispatcher
{
    public var s:Sound;
    public var sc:SoundChannel;
    public var url:String;
    public var bufferTime:int = 1000;

    public var isLoaded:Boolean = false;
    public var isReadyToPlay:Boolean = false;
    public var isPlaying:Boolean = false;
    public var isStreaming:Boolean = true;
    public var autoLoad:Boolean = true;
    public var autoPlay:Boolean = true;
        
    public var pausePosition:int = 0;
        
    public static const PLAY_PROGRESS:String = "playProgress";
    public var progressInterval:int = 1000;
    public var playTimer:Timer;
        
    public function SoundFacade(soundUrl:String, autoLoad:Boolean = true,
                                    autoPlay:Boolean = true, streaming:Boolean = true, 
                                    bufferTime:int = -1):void
    {
        this.url = soundUrl;

        // Sets Boolean values that determine the behavior of this object
        this.autoLoad = autoLoad;
        this.autoPlay = autoPlay;
        this.isStreaming = streaming;

        // Defaults to the global bufferTime value
        if (bufferTime < 0)
        {
            bufferTime = SoundMixer.bufferTime;
        }

        // Keeps buffer time reasonable, between 0 and 30 seconds
        this.bufferTime = Math.min(Math.max(0, bufferTime), 30000);
            
        if (autoLoad)
        {
            load();
        }
    }

The SoundFacade class extends the EventDispatcher class so that it can dispatch its own events. The class code first declares properties for a Sound object and a SoundChannel object. The class also stores the value of the URL of the sound file and a bufferTime property to use when streaming the sound. In addition, it accepts some Boolean parameter values that affect the loading and playback behavior:

The bufferTime parameter defaults to a value of -1. If the constructor method detects a negative value in the bufferTime parameter, it sets the bufferTime property to the value of SoundMixer.bufferTime. This lets the application default to the global SoundMixer.bufferTime value as desired.

If the autoLoad parameter is set to true, the constructor method immediately calls the following load() method to start loading the sound file:

public function load():void
{
    if (this.isPlaying)
    {
        this.stop();
        this.s.close();
    }
    this.isLoaded = false;
    
    this.s = new Sound();
    
    this.s.addEventListener(ProgressEvent.PROGRESS, onLoadProgress);
    this.s.addEventListener(Event.OPEN, onLoadOpen);
    this.s.addEventListener(Event.COMPLETE, onLoadComplete);
    this.s.addEventListener(Event.ID3, onID3);
    this.s.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
    this.s.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onIOError);
    
    var req:URLRequest = new URLRequest(this.url);
    
    var context:SoundLoaderContext = new SoundLoaderContext(this.bufferTime,
                                                                                            true);
    this.s.load(req, this.slc);
}

The load() method creates a new Sound object and then adds listeners for all of the important sound events. Then it tells the Sound object to load the sound file, using a SoundLoaderContext object to pass in the bufferTime value.

Because the url property can be changed, a SoundFacade instance can be used to play different sound files in succession: simply change the url property and call the load() method, and the new sound file will be loaded.

The following three event listener methods show how the SoundFacade object tracks loading progress and decides when to start playing the sound:

public function onLoadOpen(event:Event):void
{
    if (this.isStreaming)
    {
        this.isReadyToPlay = true;
        if (autoPlay)
        {
            this.play();
        }
    }
    this.dispatchEvent(event.clone());
}

public function onLoadProgress(event:ProgressEvent):void
{   
    this.dispatchEvent(event.clone());
}

public function onLoadComplete(event:Event):void
{
    this.isReadyToPlay = true;
    this.isLoaded = true;
    this.dispatchEvent(evt.clone());
    
    if (autoPlay && !isPlaying)
    {
        play();
    }
}

The onLoadOpen() method executes when sound loading starts. If the sound can be played in streaming mode, the onLoadComplete() method sets the isReadyToPlay flag to true right away. The isReadyToPlay flag determines whether the application can start the sound playing, perhaps in response to a user action like clicking a Play button. The SoundChannel class manages the buffering of sound data, so there is no need to explicitly check whether enough data has been loaded before calling the play() method.

The onLoadProgress() method executes periodically during the loading process. It simply dispatches a clone of its ProgressEvent object for use by code that uses this SoundFacade object.

When the sound data has been fully loaded the onLoadComplete() method executes, calling the play() method for non-streaming sounds if needed. The play() method itself is shown below.

public function play(pos:int = 0):void
{
    if (!this.isPlaying)
    {
        if (this.isReadyToPlay)
        {
            this.sc = this.s.play(pos);
            this.sc.addEventListener(Event.SOUND_COMPLETE, onPlayComplete);
            this.isPlaying = true;
            
            this.playTimer = new Timer(this.progressInterval);
            this.playTimer.addEventListener(TimerEvent.TIMER, onPlayTimer);
            this.playTimer.start();
        }
    }
}

The play() method calls the Sound.play() method if the sound is ready to play. The resulting SoundChannel object is stored in the sc property. The play() method then creates a Timer object that will be used to dispatch playback progress events at regular intervals.


Flash CS3