AS3: SoundManager v1.4

UPDATE 1/14/11: As pointed out in the comments by a few users, I just fixed a bug that didn't allow you to replay a sound once it was played for the first time. Sorry about that. Please keep testing and sending in reports!

View SoundManager Documentation
View SoundItem Documentation
Download TweenLite (needed to run example files/SoundManager)
Download Classes & Example Files

The SoundManager has been the most popular item I've ever posted on this blog and I got a lot of e-mails through the years asking to update/make changes to it and also some really good feature requests as well as people actually putting in features and passing them back to me. Admittedly I don't add very many sounds to my projects but it's always been a goal of mine to go back and update the SoundManager since it is so popular (and I'd like to put more sounds into my own work). About two years ago I had a ton of e-mails from users with bugs and requests which I meant to implement. Unfortunately I went through an e-mail migration and I ended up losing a lot of those e-mails (sorry, so if you don't see your changes and you e-mailed me, please send it again). Since then I've gotten some more e-mails and when I had time to put a new version together I did. I actually updated the classes three times and went back and forth on a bunch of issues but finally came up with this version which is pretty solid.

This (as with the previous version) has not been tested "in the wild" and could contain bugs so use at your own risk. If you do find a bug or have a feature request, please by all means contact me and I'll try my best to get it in there/fix it. I'm posting this up because I've had many requests to do so but also so that it could go through the rigors of everyday use and become a very useful library down the road for sound work in Flash. Here is a list of the features in each version that have been updated since I last posted:

1.4

1.3

1.2

1.1

Here is the new class in all its glory:

Actionscript:
  1. package com.reintroducing.sound
  2. {
  3.     import com.reintroducing.events.SoundManagerEvent;
  4.  
  5.     import flash.events.Event;
  6.     import flash.events.EventDispatcher;
  7.     import flash.events.IOErrorEvent;
  8.     import flash.events.ProgressEvent;
  9.     import flash.media.Sound;
  10.     import flash.media.SoundLoaderContext;
  11.     import flash.net.URLRequest;
  12.     import flash.utils.Dictionary;
  13.     import flash.utils.getQualifiedClassName;
  14.    
  15.     /**
  16.      * The SoundManager is a singleton that allows you to have various ways to control sounds in your project.
  17.      * <p />
  18.      * The SoundManager can load external sounds, play sounds loaded through an asset loader, or library sounds,
  19.      * pause/mute/stop/control volume for one or more sounds at a time, fade sounds up or down, and allows additional
  20.      * control to sounds not readily available through the default classes.
  21.      * <p />
  22.      * The supplementary SoundItem class is dependent on TweenLite (http://www.tweenlite.com) to aid in easily fading the volume of the sound.
  23.      *
  24.      * @author Matt Przybylski [http://www.reintroducing.com]
  25.      * @version 1.4
  26.      *
  27.      * VERSION HISTORY:
  28.      *
  29.      * 1.4
  30.      *   - General refactoring.
  31.      *   
  32.      *   - Added areAllMuted getter.  Fixed bug where if muteAllSounds() was called, playing a sound after that caused it
  33.      *     to play rather than stay muted.
  34.      *    
  35.      *   - Fixed bug where calling muteAllSounds() didn't mute sounds that were being tweened at the same time (thanks Milan Orszagh).
  36.      *   
  37.      *   - Reverted to TweenLite for default fading behavior.
  38.      *   
  39.      *   - Reconfigured SoundItem to not extend sound and be its own object so that we can add preloaded sounds without hassle.
  40.      *   
  41.      *   - Added addPreloadedSound() method to add sounds that are loaded by an external loading system (thanks Nuajan for the idea).
  42.      *   
  43.      *   - Renamed events being fired by SoundManagerEvent to be more appropriate with what's happening.
  44.      *   
  45.      *   - Added SoundManagerEvent.SOUND_ITEM_PLAY_COMPLETE event.
  46.      *   
  47.      *   - Added a destroy() method to SoundItem to clean up for garbage collection.
  48.      *
  49.      * 1.3
  50.      *   - Added the new SoundItem class so that each sound is strongly typed rather than being a generic Object.
  51.      *     When using sounds in your library, your sound MUST extend com.reintroducing.sound.SoundItem and NOT
  52.      *     flash.media.Sound.  Set this as the Base class in the properties panel for each library sound.
  53.      *   
  54.      *   - Added the getSoundItem() method and removed getSoundObject() method in favor of using the new SoundItem class.
  55.      *   
  56.      *   - Added the SoundManagerEvent and set the manager to fire off appropriate events.
  57.      *   
  58.      *   - Changed to TweenMax for default fading behavior.
  59.      *   
  60.      * 1.2
  61.      *   - Removed ability to play the sound again if it is already playing (causes conflict with stopAllSounds).
  62.      *     To play the same sound at the same time, add the sound under a differnet name (thanks Yu-Chung Chen).
  63.      *    
  64.      *   - Fixed bug where playSound() wouldn't play from specified position if the sound was paused (thanks Yu-Chung Chen).
  65.      *   
  66.      * 1.1
  67.      *   - Fixed bug where calling playSound() on a sound that was muted by muteAllSounds() was causing it to play.
  68.      *     Use unmuteAllSounds() to resume playback of muted sounds.
  69.      */
  70.     public class SoundManager extends EventDispatcher
  71.     {
  72. //- PRIVATE & PROTECTED VARIABLES -------------------------------------------------------------------------
  73.  
  74.         // singleton instance
  75.         private static var _instance:SoundManager;
  76.         private static var _allowInstance:Boolean;
  77.        
  78.         private var _soundsDict:Dictionary;
  79.         private var _sounds:Array;
  80.         private var _areAllMuted:Boolean;
  81.         private var _tempExternalSoundItem:SoundItem;
  82.        
  83. //- PUBLIC & INTERNAL VARIABLES ---------------------------------------------------------------------------
  84.        
  85.        
  86.        
  87. //- CONSTRUCTOR -------------------------------------------------------------------------------------------
  88.    
  89.         // singleton instance of SoundManager
  90.         public static function getInstance():SoundManager
  91.         {
  92.             if (_instance == null)
  93.             {
  94.                 _allowInstance = true;
  95.                 _instance = new SoundManager();
  96.                 _allowInstance = false;
  97.             }
  98.            
  99.             return _instance;
  100.         }
  101.        
  102.         public function SoundManager()
  103.         {
  104.             this._soundsDict = new Dictionary(true);
  105.             this._sounds = [];
  106.            
  107.             if (!_allowInstance)
  108.             {
  109.                 throw new Error("Error: Use SoundManager.getInstance() instead of the new keyword.");
  110.             }
  111.         }
  112.        
  113. //- PRIVATE & PROTECTED METHODS ---------------------------------------------------------------------------
  114.        
  115.         /**
  116.          *
  117.          */
  118.         private function addSound($linkageID:*, $preloadedSound:Sound, $path:String, $name:String, $buffer:Number = 1000, $checkPolicyFile:Boolean = false):Boolean
  119.         {
  120.             // check to see if sound already exists by the specified name
  121.             var len:int = _sounds.length;
  122.            
  123.             for (var i:int = 0; i <len; i++)
  124.             {
  125.                 if ((_sounds[i] as SoundItem).name == $name) return false;
  126.             }
  127.            
  128.             // sound doesn't exist yet, go ahead and create it
  129.             var si:SoundItem = new SoundItem();
  130.            
  131.             if ($linkageID == null)
  132.             {
  133.                 if ($preloadedSound == null)
  134.                 {
  135.                     // adding external sound
  136.                     si.sound = new Sound(new URLRequest($path), new SoundLoaderContext($buffer, $checkPolicyFile));
  137.                     si.sound.addEventListener(IOErrorEvent.IO_ERROR, onSoundLoadError);
  138.                     si.sound.addEventListener(ProgressEvent.PROGRESS, onSoundLoadProgress);
  139.                     si.sound.addEventListener(Event.COMPLETE, onSoundLoadComplete);
  140.                    
  141.                     _tempExternalSoundItem = si;
  142.                 }
  143.                 else
  144.                 {
  145.                     // adding a preloaded sound
  146.                     si.sound = $preloadedSound;
  147.                 }
  148.             }
  149.             else
  150.             {
  151.                 // adding library sound
  152.                 si.sound = new $linkageID;
  153.             }
  154.            
  155.             si.name = $name;
  156.             si.position = 0;
  157.             si.paused = true;
  158.             si.volume = (_areAllMuted) ? 0 : 1;
  159.             si.savedVolume = si.volume;
  160.             si.startTime = 0;
  161.             si.loops = 0;
  162.             si.pausedByAll = false;
  163.             si.muted = _areAllMuted;
  164.             si.addEventListener(SoundManagerEvent.SOUND_ITEM_PLAY_COMPLETE, handleSoundPlayComplete);
  165.            
  166.             _soundsDict[$name] = si;
  167.             _sounds.push(si);
  168.            
  169.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_ADDED, si));
  170.            
  171.             return true;
  172.         }
  173.        
  174. //- PUBLIC & INTERNAL METHODS -----------------------------------------------------------------------------
  175.    
  176.         /**
  177.          * Adds a sound from the library to the sounds dictionary for playing in the future.
  178.          *
  179.          * @param $linkageID The class name of the library symbol that was exported for AS
  180.          * @param $name The string identifier of the sound to be used when calling other methods on the sound
  181.          *
  182.          * @return Boolean A boolean value representing if the sound was added successfully
  183.          */
  184.         public function addLibrarySound($linkageID:*, $name:String):Boolean
  185.         {
  186.             return addSound($linkageID, null, "", $name);
  187.         }
  188.        
  189.         /**
  190.          * Adds an external sound to the sounds dictionary for playing in the future.
  191.          *
  192.          * @param $path A string representing the path where the sound is on the server
  193.          * @param $name The string identifier of the sound to be used when calling other methods on the sound
  194.          * @param $buffer The number, in milliseconds, to buffer the sound before you can play it (default: 1000)
  195.          * @param $checkPolicyFile A boolean that determines whether Flash Player should try to download a cross-domain policy file from the loaded sound's server before beginning to load the sound (default: false)
  196.          *
  197.          * @return Boolean A boolean value representing if the sound was added successfully
  198.          */
  199.         public function addExternalSound($path:String, $name:String, $buffer:Number = 1000, $checkPolicyFile:Boolean = false):Boolean
  200.         {
  201.             return addSound(null, null, $path, $name, $buffer, $checkPolicyFile);
  202.         }
  203.        
  204.         /**
  205.          * Adds a sound that was preloaded by an external library to the sounds dictionary for playing in the future.
  206.          *
  207.          * @param $sound The sound object that was preloaded
  208.          * @param $name The string identifier of the sound to be used when calling other methods on the sound
  209.          *
  210.          * @return Boolean A boolean value representing if the sound was added successfully
  211.          */
  212.         public function addPreloadedSound($sound:Sound, $name:String):Boolean
  213.         {
  214.             return addSound(null, $sound, "", $name);
  215.         }
  216.        
  217.         /**
  218.          * Removes a sound from the sound dictionary.  After calling this, the sound will not be available until it is re-added.
  219.          *
  220.          * @param $name The string identifier of the sound to remove
  221.          *
  222.          * @return void
  223.          */
  224.         public function removeSound($name:String):void
  225.         {
  226.             var len:int = _sounds.length;
  227.             var si:SoundItem;
  228.            
  229.             for (var i:int = 0; i <len; i++)
  230.             {
  231.                 si = (_sounds[i] as SoundItem);
  232.                
  233.                 if (si.name == $name)
  234.                 {
  235.                     si.sound.removeEventListener(IOErrorEvent.IO_ERROR, onSoundLoadError);
  236.                     si.sound.removeEventListener(ProgressEvent.PROGRESS, onSoundLoadProgress);
  237.                     si.sound.removeEventListener(Event.COMPLETE, onSoundLoadComplete);
  238.                     si.removeEventListener(SoundManagerEvent.SOUND_ITEM_PLAY_COMPLETE, handleSoundPlayComplete);
  239.                     si.destroy();
  240.                    
  241.                     _sounds.splice(i, 1);
  242.                    
  243.                     break;
  244.                 }
  245.             }
  246.            
  247.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_REMOVED, (_soundsDict[$name] as SoundItem)));
  248.            
  249.             delete (_soundsDict[$name] as SoundItem);
  250.         }
  251.        
  252.         /**
  253.          * Removes all sounds from the sound dictionary.
  254.          *
  255.          * @return void
  256.          */
  257.         public function removeAllSounds():void
  258.         {
  259.             var len:int = _sounds.length;
  260.             var si:SoundItem;
  261.            
  262.             for (var i:int = 0; i <len; i++)
  263.             {
  264.                 si = (_sounds[i] as SoundItem);
  265.                 si.sound.removeEventListener(IOErrorEvent.IO_ERROR, onSoundLoadError);
  266.                 si.sound.removeEventListener(ProgressEvent.PROGRESS, onSoundLoadProgress);
  267.                 si.sound.removeEventListener(Event.COMPLETE, onSoundLoadComplete);
  268.             }
  269.            
  270.             _sounds = [];
  271.             _soundsDict = new Dictionary(true);
  272.            
  273.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.REMOVED_ALL));
  274.         }
  275.  
  276.         /**
  277.          * Plays or resumes a sound from the sound dictionary with the specified name.  If the sounds in the dictionary were muted by
  278.          * the muteAllSounds() method, no sounds are played until unmuteAllSounds() is called.
  279.          *
  280.          * @param $name The string identifier of the sound to play
  281.          * @param $volume A number from 0 to 1 representing the volume at which to play the sound (default: 1)
  282.          * @param $startTime A number (in milliseconds) representing the time to start playing the sound at (default: 0)
  283.          * @param $loops An integer representing the number of times to loop the sound (default: 0)
  284.          * @param $resumeTween A boolean that indicates if a faded sound's volume should resume from the last saved state (default: true)
  285.          *
  286.          * @return void
  287.          */
  288.         public function playSound($name:String, $volume:Number = 1, $startTime:Number = 0, $loops:int = 0, $resumeTween:Boolean = true):void
  289.         {
  290.             var si:SoundItem = (_soundsDict[$name] as SoundItem);
  291.             si.play($startTime, $loops, $volume, $resumeTween);
  292.            
  293.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_PLAY_START, si));
  294.         }
  295.        
  296.         /**
  297.          * Pauses the specified sound.
  298.          *
  299.          * @param $name The string identifier of the sound
  300.          * @param $pauseTween A boolean that either pauses the fadeTween or allows it to continue (default: true)
  301.          *
  302.          * @return void
  303.          */
  304.         public function pauseSound($name:String, $pauseTween:Boolean = true):void
  305.         {
  306.             var si:SoundItem = (_soundsDict[$name] as SoundItem);
  307.             si.pause($pauseTween);
  308.            
  309.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_PAUSE, si));
  310.         }
  311.        
  312.         /**
  313.          * Stops the specified sound.
  314.          *
  315.          * @param $name The string identifier of the sound
  316.          *
  317.          * @return void
  318.          */
  319.         public function stopSound($name:String):void
  320.         {
  321.             var si:SoundItem = (_soundsDict[$name] as SoundItem);
  322.             si.stop();
  323.            
  324.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_STOP, si));
  325.         }
  326.        
  327.         /**
  328.          * Plays all the sounds that are in the sound dictionary.
  329.          *
  330.          * @param $resumeTweens A boolean that resumes all unfinished fade tweens (default: true)
  331.          * @param $useCurrentlyPlayingOnly A boolean that only plays the sounds which were currently playing before a pauseAllSounds() or stopAllSounds() call (default: false)
  332.          *
  333.          * @return void
  334.          */
  335.         public function playAllSounds($resumeTweens:Boolean = true, $useCurrentlyPlayingOnly:Boolean = false):void
  336.         {
  337.             var len:int = _sounds.length;
  338.            
  339.             for (var i:int = 0; i <len; i++)
  340.             {
  341.                 var id:String = (_sounds[i] as SoundItem).name;
  342.                
  343.                 if ($useCurrentlyPlayingOnly)
  344.                 {
  345.                     if ((_soundsDict[id] as SoundItem).pausedByAll)
  346.                     {
  347.                         (_soundsDict[id] as SoundItem).pausedByAll = false;
  348.                         playSound(id, (_soundsDict[id] as SoundItem).volume, 0, 0, $resumeTweens);
  349.                     }
  350.                 }
  351.                 else
  352.                 {
  353.                     playSound(id, (_soundsDict[id] as SoundItem).volume, 0, 0, $resumeTweens);
  354.                 }
  355.             }
  356.            
  357.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.PLAY_ALL));
  358.         }
  359.        
  360.         /**
  361.          * Pauses all the sounds that are in the sound dictionary.
  362.          *
  363.          * @param $pauseTweens A boolean that either pauses each SoundItem's fadeTween or allows them to continue (default: true)
  364.          * @param $useCurrentlyPlayingOnly A boolean that only pauses the sounds which are currently playing (default: true)
  365.          *
  366.          * @return void
  367.          */
  368.         public function pauseAllSounds($pauseTweens:Boolean = true, $useCurrentlyPlayingOnly:Boolean = true):void
  369.         {
  370.             var len:int = _sounds.length;
  371.            
  372.             for (var i:int = 0; i <len; i++)
  373.             {
  374.                 var id:String = (_sounds[i] as SoundItem).name;
  375.                
  376.                 if ($useCurrentlyPlayingOnly)
  377.                 {
  378.                     if (!(_soundsDict[id] as SoundItem).paused)
  379.                     {
  380.                         (_soundsDict[id] as SoundItem).pausedByAll = true;
  381.                         pauseSound(id, $pauseTweens);
  382.                     }
  383.                 }
  384.                 else
  385.                 {
  386.                     pauseSound(id, $pauseTweens);
  387.                 }
  388.             }
  389.            
  390.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.PAUSE_ALL));
  391.         }
  392.        
  393.         /**
  394.          * Stops all the sounds that are in the sound dictionary.
  395.          *
  396.          * @param $useCurrentlyPlayingOnly A boolean that only stops the sounds which are currently playing (default: true)
  397.          *
  398.          * @return void
  399.          */
  400.         public function stopAllSounds($useCurrentlyPlayingOnly:Boolean = true):void
  401.         {
  402.             var len:int = _sounds.length;
  403.            
  404.             for (var i:int = 0; i <len; i++)
  405.             {
  406.                 var id:String = (_sounds[i] as SoundItem).name;
  407.                
  408.                 if ($useCurrentlyPlayingOnly)
  409.                 {
  410.                     if (!(_soundsDict[id] as SoundItem).paused)
  411.                     {
  412.                         (_soundsDict[id] as SoundItem).pausedByAll = true;
  413.                         stopSound(id);
  414.                     }
  415.                 }
  416.                 else
  417.                 {
  418.                     stopSound(id);
  419.                 }
  420.             }
  421.            
  422.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.STOP_ALL));
  423.         }
  424.        
  425.         /**
  426.          * Fades the sound to the specified volume over the specified amount of time.
  427.          *
  428.          * @param $name The string identifier of the sound
  429.          * @param $targVolume The target volume to fade to, between 0 and 1 (default: 0)
  430.          * @param $fadeLength The time to fade over, in seconds (default: 1)
  431.          * @param $stopOnComplete Added by Danny Miller from K2xL, stops the sound once the fade is done if set to true
  432.          *
  433.          * @return void
  434.          */
  435.         public function fadeSound($name:String, $targVolume:Number = 0, $fadeLength:Number = 1, $stopOnComplete:Boolean = false):void
  436.         {
  437.             var si:SoundItem = (_soundsDict[$name] as SoundItem);
  438.             si.addEventListener(SoundManagerEvent.SOUND_ITEM_FADE_COMPLETE, handleFadeComplete);
  439.             si.fade($targVolume, $fadeLength, $stopOnComplete);
  440.            
  441.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_FADE, si));
  442.         }
  443.        
  444.         /**
  445.          * Mutes the volume for all sounds in the sound dictionary.
  446.          *
  447.          * @return void
  448.          */
  449.         public function muteAllSounds():void
  450.         {
  451.             _areAllMuted = true;
  452.            
  453.             var len:int = _sounds.length;
  454.             var id:String;
  455.             var si:SoundItem;
  456.            
  457.             for (var i:int = 0; i <len; i++)
  458.             {
  459.                 id = (_sounds[i] as SoundItem).name;
  460.                 si = (_soundsDict[id] as SoundItem);
  461.                 si.savedVolume = si.channel.soundTransform.volume;
  462.                 si.muted = true;
  463.                
  464.                 setSoundVolume(id, 0);
  465.             }
  466.            
  467.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.MUTE_ALL));
  468.         }
  469.        
  470.         /**
  471.          * Resets the volume to their original setting for all sounds in the sound dictionary.
  472.          *
  473.          * @return void
  474.          */
  475.         public function unmuteAllSounds():void
  476.         {
  477.             _areAllMuted = false;
  478.            
  479.             var len:int = _sounds.length;
  480.             var id:String;
  481.             var si:SoundItem;
  482.            
  483.             for (var i:int = 0; i <len; i++)
  484.             {
  485.                 id = (_sounds[i] as SoundItem).name;
  486.                 si = (_soundsDict[id] as SoundItem);
  487.                 si.muted = false;
  488.                
  489.                 setSoundVolume(id, si.savedVolume);
  490.             }
  491.            
  492.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.UNMUTE_ALL));
  493.         }
  494.        
  495.         /**
  496.          * Sets the volume of the specified sound.
  497.          *
  498.          * @param $name The string identifier of the sound
  499.          * @param $volume The volume, between 0 and 1, to set the sound to
  500.          *
  501.          * @return void
  502.          */
  503.         public function setSoundVolume($name:String, $volume:Number):void
  504.         {
  505.             (_soundsDict[$name] as SoundItem).setVolume($volume);
  506.         }
  507.        
  508.         /**
  509.          * Gets the volume of the specified sound.
  510.          *
  511.          * @param $name The string identifier of the sound
  512.          *
  513.          * @return Number The current volume of the sound
  514.          */
  515.         public function getSoundVolume($name:String):Number
  516.         {
  517.             return (_soundsDict[$name] as SoundItem).channel.soundTransform.volume;
  518.         }
  519.        
  520.         /**
  521.          * Gets the position of the specified sound.
  522.          *
  523.          * @param $name The string identifier of the sound
  524.          *
  525.          * @return Number The current position of the sound, in milliseconds
  526.          */
  527.         public function getSoundPosition($name:String):Number
  528.         {
  529.             return (_soundsDict[$name] as SoundItem).channel.position;
  530.         }
  531.        
  532.         /**
  533.          * Gets the duration of the specified sound.
  534.          *
  535.          * @param $name The string identifier of the sound
  536.          *
  537.          * @return Number The length of the sound, in milliseconds
  538.          */
  539.         public function getSoundDuration($name:String):Number
  540.         {
  541.             return (_soundsDict[$name] as SoundItem).sound.length;
  542.         }
  543.        
  544.         /**
  545.          * Gets the SoundItem instance of the specified sound.
  546.          *
  547.          * @param $name The string identifier of the SoundItem
  548.          *
  549.          * @return SoundItem The SoundItem
  550.          */
  551.         public function getSoundItem($name:String):SoundItem
  552.         {
  553.             return (_soundsDict[$name] as SoundItem);
  554.         }
  555.        
  556.         /**
  557.          * Identifies if the sound is paused or not.
  558.          *
  559.          * @param $name The string identifier of the sound
  560.          *
  561.          * @return Boolean The boolean value of paused or not paused
  562.          */
  563.         public function isSoundPaused($name:String):Boolean
  564.         {
  565.             return (_soundsDict[$name] as SoundItem).paused;
  566.         }
  567.        
  568.         /**
  569.          * Identifies if the sound was paused or stopped by calling the stopAllSounds() or pauseAllSounds() methods.
  570.          *
  571.          * @param $name The string identifier of the sound
  572.          *
  573.          * @return Number The boolean value of pausedByAll or not pausedByAll
  574.          */
  575.         public function isSoundPausedByAll($name:String):Boolean
  576.         {
  577.             return (_soundsDict[$name] as SoundItem).pausedByAll;
  578.         }
  579.    
  580. //- EVENT HANDLERS ----------------------------------------------------------------------------------------
  581.    
  582.         /**
  583.          * Dispatched when an external sound can't load and produces an error.
  584.          */
  585.         private function onSoundLoadError($evt:IOErrorEvent):void
  586.         {
  587.             _tempExternalSoundItem = null;
  588.            
  589.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_LOAD_ERROR));
  590.         }
  591.        
  592.         /**
  593.          * Dispatched when an external sound is loading.
  594.          */
  595.         private function onSoundLoadProgress($evt:ProgressEvent):void
  596.         {
  597.             var percent:uint = Math.round(100 * ($evt.bytesLoaded / $evt.bytesTotal));
  598.             var snd:Sound = ($evt.target as Sound);
  599.             var duration:Number = 0;
  600.            
  601.             if (snd && snd.length> 0)
  602.             {
  603.                 duration = ((snd.bytesTotal / (snd.bytesLoaded / snd.length)) * .001);
  604.             }
  605.            
  606.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_LOAD_PROGRESS, _tempExternalSoundItem, duration, percent));
  607.         }
  608.        
  609.         /**
  610.          * Dispatched when an external sound is fully loaded.
  611.          */
  612.         private function onSoundLoadComplete($evt:Event):void
  613.         {
  614.             var snd:Sound = ($evt.target as Sound);
  615.             var duration:Number = (snd.length * .001);
  616.            
  617.             dispatchEvent(new SoundManagerEvent(SoundManagerEvent.SOUND_ITEM_LOAD_COMPLETE, _tempExternalSoundItem, duration));
  618.            
  619.             _tempExternalSoundItem = null;
  620.         }
  621.        
  622.         /**
  623.          * Dispatched once a sound's fadeTween is completed if the sound was called to fade.
  624.          */
  625.         private function handleFadeComplete($evt:SoundManagerEvent):void
  626.         {
  627.             dispatchEvent($evt);
  628.            
  629.             var si:SoundItem = $evt.soundItem;
  630.             si.removeEventListener(SoundManagerEvent.SOUND_ITEM_FADE_COMPLETE, handleFadeComplete);
  631.         }
  632.        
  633.         /**
  634.          * Dispatched when a SoundItem has finished playback.
  635.          */
  636.         private function handleSoundPlayComplete($evt:SoundManagerEvent):void
  637.         {
  638.             dispatchEvent($evt);
  639.         }
  640.    
  641. //- GETTERS & SETTERS -------------------------------------------------------------------------------------
  642.        
  643.         /**
  644.          *
  645.          */
  646.         public function get sounds():Array
  647.         {
  648.             return _sounds;
  649.         }
  650.        
  651.         /**
  652.          *
  653.          */
  654.         public function get areAllMuted():Boolean
  655.         {
  656.             return _areAllMuted;
  657.         }
  658.    
  659. //- HELPERS -----------------------------------------------------------------------------------------------
  660.    
  661.         override public function toString():String
  662.         {
  663.             return getQualifiedClassName(this);
  664.         }
  665.    
  666. //- END CLASS ---------------------------------------------------------------------------------------------
  667.     }
  668. }

If you found this post useful, please consider leaving a comment, subscribing to the feed, or making a small donation.

44 Comments

[...] This post was mentioned on Twitter by Matt Przybylski and Paul Johnson. Paul Johnson said: AS3: SoundManager v1.4 http://icio.us/EoDcsp [...]

I use your class in my project and i can't understand, why my sounds play only once when i try to Listen an MouseEvent.CLICK or MouseEvent.MOUSE_OVER the sound is play one time and when the Event happen and then the sound dosn't play again...???

@Vadim: If you're trying to play the same sound multiple times at the same time (or quickly pressing the button to re-use the same sound and re-play over the other sound) you can't currently do that. There were some limitations I had to impose on playback and that was one of them. You have to create a new sound item if for each sound (even if it is the same one) that you want to play at the same time. They should, however, play one after the other as long as the first play call has finished, you can re-use the same sound item to play that sound again. If it doesn't work for you, please send me an isolated example and I can take a look at it. Thanks.

Ok, Matt i'll try your recommendation, i need to play the same sound multiple times in the game.

Tnks a lot and Happy New Year, your Class is super!!!

@Vadim: That shouldn't be a problem, you should be able to play the same sound multiple times over, just can't play it multiple times at the same time unless you create multiple instances of it. You could use something like an object pool to track this and pick one out of the pool when you need to play it. Hope that helps.

hi, great class! thanks!!

i too am finding that once i play a sound i cant replay it at a later time - trying to find a solution...

this line works the first time but thereafter no sound is played:

SoundManager.getInstance().playSound("squeak");

note: i can play sounds repeatedly when i comment out the following line in the SoundItem class in the 'play' method:

//if (!paused) return;

@brendan: Good catch, very sorry about that guys, I forgot to clear the paused property in the complete of the sound. Go ahead and re-download the class, it now works as it should. Thanks for the heads up.

Please, everyone, keep on testing this and finding bugs, this is why I wanted to put it out there so it can be tested by other devs :)

Hi, thanks for sharing your code!
I also need to play a sound multiple at the same time (button mouse over). Therefore i added a function playSoundDirect() that simply calls play() on the soundItem.sound object.
In this case (button mouseover) i do not need a soundchannel reference or a sound_complete event. Calling sound.play() direct serves the purpose.

improvement suggestion:
To avoid errors when a sound is not registerd or already removed, the SoundManager functions playSound(), pauseSound() ... should check first that the _soundsDict contains the requested SoundItem.

@Björn: If you've made additions/fixes to the code, it'd be greatly appreciated if you could send me the updated files with a quick quote/comments on where/what you changed. You could send it to matt [at] reintroducing dot com. Thanks!

"Fixed bug where if muteAllSounds() was called, playing a sound after that caused it to play rather than stay muted". Doesn't appear to work. In the SoundItem class I added "if (muted) volume = 0;" to the play method and that seems to have fixed it.

Possible issue...SoundItem is version 1.2, but the release is version 1.4. Maybe an old build? Thanks!

Another suggested fix...in some versions of the Flash player, the position of looping sounds can be erroneously reported, leading to a null channel being returned.

In the SoundItem class, in the play() method, right before calling sound.play() insert this line:

position = position%sound.length;

This will limit the position to the length of the sound. Cheers!

@Rob: Thanks, I'll take a look at this stuff, just got back from hiatus :)

@Rob @Matt. Yup. seemed old. Its 1.2. However, if SoundManager is 1.4, all I did to fix this mute issue for me was added this line into 's function right after .

if (muted) return;

Apparently, you already had a public var but was not used in this class.

Thanks for this great library btw. Its Awesome.

Tahir - I think you're better of doing as I suggested ( if (muted) volume = 0 ) otherwise your sound will not be silently playing in the background (and hence not be muted). Depends on your needs, I suppose.

@Rob nah if I set to the sound still plays. Anyway. I think I'll stick to the old line I put.

I have a little-big problem with the library SoundManager. when you run the PlaySound method, uses the play method of the library flash.media.Sound. According to the documentation, this method should return a channel, but may also return a Null ( http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/Sound.html#play() )
Has not seen this option, and I get many times.

hey Matt!!
why you don't upload this project to your github account??? it would be nice!!
i already made a copy at https://github.com/GrupoW/as3-Sound-Manager, but should be forked from your original repo XD

cheers!.

@Raul: I have not yet jumped on the Git bandwagon but have considered it :) I see you added an addMultipleSounds method. Is that all you added or were there other additions? That's a good idea, by the way, I'll have to add that in!

well another addition that i made was to hide the getDefinitionByName call by creating a public method addSound that look like this:

public function addSound($linkageID:String):Boolean {
return registerSound(getDefinitionByName($linkageID), null, "", $linkageID);
}

this method also apply the linkageID as the same as the SoundID, because i notice that i always ended up putting SoundManager.addLibrarySound("clickSound", "clickSound");

another one is that if you try to do something with a sound that it is not added (for example, play or fade) the SoundManager warning me with the next message

Error: The string identifier [SD_OPEN] of the sound to fade is not added
at com.reintroducing.sound::SoundManager/fadeSound()
at sounds_fla::MainTimeline/frame1()

the cool thing is that my application still runs and it is not crashed by referring a null object.

[...] скористатися вже готовими класами, такими як SoundManager. Але особисто мені не дуже подобається ідея [...]

Hi Matt,

i was looking for a good sound manager in as3, and suddenly found you website via google :) .

this is such a nice library and also i wanted to add some more function's like getting the status of given sound and to check if is currently playing or not.
but it seems i am missing something here.

i just wanted to add a function to know if a particular sound is currently playing or not.

here is the snippet of code, please let me know what i am missing here.

/**
* @author Manoj Sahu
* @param $name name of sound class.
* @return returns true if sound is playing.
*/
public function isSoundPlaying($name:String):Boolean {
if (this._soundsDict[$name].channel.position < this._soundsDict[$name].sound.length) {
return true;
}
return false;
}

@Manoj: You can use the isSoundPaused($name) method to check if it is playing or not :)

it looks like i was using older version of SoundManager, that why i was wondering that why there is no such method to check if sound is playing.

thanks for making this sound manager event based ( i am huge fan of event driven programming).

[...] Management – Sound Manager v1.4 – Download I recently discovered Sound Manager and so far it has helped me out a great deal. There’s [...]

Hello. It would be nice to have an explicit licence to go with the code. I'd suggest the MIT licence, but that's only because it is so liberal.

Thanks.

@Jos: The code is licensed under the "do whatever the hell you want with it" license :) I don't believe in putting licenses on open source code; isn't that the meaning of "open source"?

When you use the pauseAllSounds function and you call playAllSounds with useCurrentlyPlayingOnly=true, you should set the values of loop and position to the last ones.

re: Matt/Licences

Well, yes, sort of. However the way copyright and these things work is that, unless you do make it clear that you are releasing it under an explicit license, it is still considered to be yours expressly. By adding a license you make people who want to use the code in commercial projects understand clearly if they can or can not.

You are, of course, under no obligation to licence it in anyway at all! I'm just suggesting that for some people, having an explicit licence (for the lawyers) will make using and adopting your work easier and safer.

here is an open licence from Creative Commons that you could use:

http://bit.ly/l5oUon

they have great licences, but, as mentioned before, the MIT & BSD ones are also extremely open.

Regardless, thanks for this!
Jos

This library is very handy, thanks for sharing your code, however i found that if you use it for playing music and sounds you cannot mute only the music or mute only the sounds unless you kinnda add them to a different dictionary and code different methods, unless there is something i'm missing.

Regardless, thanks !

awesome!!! thanks for sharing your code

Thanks for this awesome piece of code! Great job!

I've been using it in a couple of my projects and recently a client pointed out this bug:
In win 7 when you unplug your headphone the sound.play(..) will return null.

Apparently this is a known issue and is fixed with something like this:

var sound:Sound = new Sound();
var channel:SoundChannel = sound.play(0, loops);
if (!channel) return null; ß add some sort of check

channel.

I tried to fix it, but I'm on a mac and don't have access to a win7 computer which makes bugtesting really hard. Maybe you can do an update?
thanks!

@Martijn: Unfortunately I too am on OSX and don't have access to a Win7 machine. Can anyone confirm/deny/help with this bug?

I made some changes to make working with embedded sounds alot easier. ie:

[Embed("Click.mp3")]
public static var Click:Class;

I made it so $name can now be a class or a string, if it's a class, we can use it's qualifiedClassName as the name. This way I can just use the class itself as the name.

manager.addSound(Audio.CLICK, Audio.CLICK);

This also opened the door to another nice trick, I don't even need to call addSound, since playSound can now accept a class, I can just have SoundManager call addSound() internally if that class hasn't been added before.

So, loading and playing a new sound, is as simple as a one liner:
manager.playSound(Audio.CLICK);

This is sweet cause it keeps overhead to minimum. No constants to define, and maintain, no big bootstrap function where you addSound() everything, no string literals to remember. Just one static class file with all your embeds.

Lemme know if you're interested, it was a very minor change, not sure what edge cases this might expose though...

@Shawn: Please do send me a copy of your changes. If you could detail in the email what lines you changed/added (estimate at least) that'd be great so I can take a look. I've got multiple versions of this thing saved now with experimental features and am getting a bit lost in all of it so I need to keep track of changes as best as possible at this point :) You can email matt at reintroducing. Thanks!

Hey Shawn and Matt - did you guys make any progress with implementing Shawn's updates to the SoundManager class? I would be very interested in using those shortcuts and embedding the sounds at runtime.

I'm having some problems referencing library sounds from an exported SWC - the soundmanager class cant find them when I do:

manageSound.addLibrarySound(getDefinitionByName("soundLibraryNameinSWC"), "soundName");

Cheers,
Ben

@Benje: Unfortunately I have not had a chance to do this yet. My wife is 8+ months pregnant so I've not had a chance to do anything, really :) It will probably still be a while before I get to it, sorry :\

Hi Matt,

Your SoundManager class has been a great help to us. One thing I noticed while doing some memory profiling is that the removeSound method does not properly remove the sound from memory. This is caused by the following line:

delete (_soundsDict[$name] as SoundItem);

Instead, the line needs to be change to:

delete _soundsDict[$name]

The reason it is not being GC'd is because (_soundsDict[$name] as SoundItem) is evaluated to a SoundItem object reference. So you are effectively saying delete [Object SoundItem] which has no effect. I've made this change in my version of your class and verified that the sound does indeed get GC'd now.

@Ryan: Thanks, glad the SM has helped. Thanks for the profiling catch as well, I never really stress tested this class as I honestly never did that much sound in my projects so it's always good when it gets put to the test. I haven't been doing much Flash lately (yep, that JS thing...) but I'll add this into the next release whenever that will be :) Thanks again.

When I call SoundManager.getInstance().muteAllSounds() it only seems to mute everything that's currently playing. If I play anything in the future, it'll ignore the fact that I have recently called mute on everything. Any reason for this?

Otherwise great sound manager!

@SM2: Good catch. I went through a couple of iterations of the SoundItem class and I think somewhere along the line that got lost in the shuffle. A check in the play method for muted should fix it if you'd like to add it in for now. Again, I'll have to update that in a future version but for the time being unfortunately my AS3 work is not a priority :\

Yeah right now I am messing around with the playSound function to see if there is some way to leverage "if (!_areAllMuted)" to play the sounds -- the problem is that simply throwing things into that if-statement meant if I play a new song while mute is active, it won't be playing when I unmute.

I'll add another comment here later if I figure it out -- thanks for the speedy reply!

Also ran into this crazy bug (more of a TweenLite implementation issue) but I had a few objects that were seemingly unrelated to the SoundManager not getting GC'd. Turns out that TweenLite instances have a nextNode property that points to the next Tween instance that was created (probably a linked list type of implementation for performance reasons). The SoundItem objects that are created by SoundManager have a tween instance that they keep around when you call fadeSound. If that sound is never destroyed (which it might not be if you want to keep the sound around to play every so often), then the next TweenLite.to call you make anywhere in your code will be referenced by that SoundItems tween object, keeping it in memory (and since that Tween will reference one of your objects to tween, that object will never be GC'd). My fix was to simply null out the tween reference in SoundItem when the fade was complete and create a new Tween instance each time fadeSound is called. Not ideal but it is better than getting stuck with a larger object in memory.

I believe this is the fix to the playSound issue:

http://pastebin.com/JAFQZtVG

What it will do is play the sound as normal, but then check if everything is muted after dispatching the sounditem play event. If _allAreMuted is true, then it'll mute the item, save the intended volume as denoted in the passed parameter, and then set the volume of the sound to 0 immediately.

Leave a comment

(required)

(required)