{"id":91,"date":"2016-08-03T19:59:13","date_gmt":"2016-08-03T19:59:13","guid":{"rendered":"https:\/\/www.spaceflint.com\/?p=91"},"modified":"2025-04-21T05:58:15","modified_gmt":"2025-04-21T05:58:15","slug":"audioplayer-for-unity-on-android-and-ios","status":"publish","type":"post","link":"https:\/\/www.spaceflint.com\/?p=91","title":{"rendered":"AudioPlayer for Unity on Android and iOS"},"content":{"rendered":"<p>Many Android devices exhibit varying degrees of latency when playing sound via the Unity <a title=\"AudioSource\" href=\"https:\/\/docs.unity3d.com\/Manual\/class-AudioSource.html\">AudioSource<\/a>.<\/p>\n<p>This is a <a title=\"well\" href=\"http:\/\/forum.unity3d.com\/threads\/noticable-delay-when-playing-an-audioclip-suggestions.87131\/\">well<\/a> <a title=\"known\" href=\"http:\/\/forum.unity3d.com\/threads\/need-a-fix-for-audio-lag-android.267620\/\">known<\/a> <a title=\"issue\" href=\"http:\/\/forum.unity3d.com\/threads\/android-sound-latency-fix.319943\/\">issue<\/a> and there are already several solutions out there to work around<br \/>\nthe problem.  This is generally achieved by using the Android <a title=\"SoundPool\" href=\"https:\/\/developer.android.com\/reference\/android\/media\/SoundPool.html\">SoundPool<\/a> service.  The solution offered here is no different in that regard.<\/p>\n<p>The <strong><a title=\"AudioPlayer.cs\" href=\"https:\/\/www.spaceflint.com\/AudioPlayer.cs.txt\">AudioPlayer.cs<\/a><\/strong> class provides a platform-neutral API, and two implementations &#8212; uses <a title=\"SoundPool\" href=\"https:\/\/developer.android.com\/reference\/android\/media\/SoundPool.html\">SoundPool<\/a> on Android, and Unity <a title=\"AudioSource\" href=\"https:\/\/docs.unity3d.com\/Manual\/class-AudioSource.html\">AudioSource<\/a> on other platforms.  Compiler #if directives select which implementation to build, according to the target platform.<\/p>\n<p>Download <a title=\"AudioPlayer.cs\" href=\"https:\/\/www.spaceflint.com\/AudioPlayer.cs.txt\">AudioPlayer.cs<\/a> here. (right-click Save As.)<\/p>\n<h2>Project and Scene Setup<\/h2>\n<p><strong>DSP Buffer Size<\/strong><\/p>\n<p>For best performance on iOS, the <strong>DSP Buffer Size<\/strong> parameter in <a title=\"AudioManager\" href=\"https:\/\/docs.unity3d.com\/Manual\/class-AudioManager.html\">AudioManager<\/a> should be set to <strong>Best latency<\/strong>, which translates to a DSP Buffer Size of <strong>256KB<\/strong>.  When running within the Unity editor, the AudioPlayer class will check and display a warning (via Debug.Assert) if this is not the case.<\/p>\n<p><strong>Placement in Scene<\/strong><\/p>\n<p>Create an empty <strong>GameObject<\/strong>, then use <strong>Add Component<\/strong> to add the script <strong>AudioPlayer.cs<\/strong> (Audio Player).<\/p>\n<p>It is ok to add more script components to this GameObject.  For example, if the scene includes a sound manager class that decides when to play effects, it is possible to place that script on the same GameObject.  The example provided below is one such very simple sound manager class.<\/p>\n<p><strong>Sound Files<\/strong><\/p>\n<p>This class expects sound files as MP3 files that reside within the <a title=\"StreamingAssets\" href=\"https:\/\/docs.unity3d.com\/Manual\/StreamingAssets.html\">StreamingAssets<\/a> directory, which should be placed directly below the Assets folder.<\/p>\n<h2>Component Properties<\/h2>\n<p><strong>NumChannels (int)<\/strong> determines the maximum number of effects that can play at once.  Each channel can play only one sound at a time, but sounds from all channels will be mixed together.  The default is <strong>4<\/strong> channels.<\/p>\n<p>On Android, this value is used during the creation of the SoundPool object.  On other platforms, this determines the number of Unity AudioSource objects which will be created.<\/p>\n<p>For API methods that expect a channel number, specify the channel as a number between zero (0) and <strong>NumChannels<\/strong>.<\/p>\n<p><strong>Sound (boolean)<\/strong> is set to false if all sound should be muted.  It is probably easier to adjust this property using the AudioPlayer.SetSound() method, which does not require a reference to the GameObject containing the AudioPlayer script component.<\/p>\n<h2>Using the API &#8211; Example Code<\/h2>\n<pre class=\"brush: csharp; gutter: false; title: ; notranslate\" title=\"\">\nusing UnityEngine;\nusing System.Collections;\n\npublic class SoundManager : MonoBehaviour {\n\npublic PlayerObject player;\n\n\/\/ below constants assume the default of four channels in AudioPlayer\nconst int firstChannel = 0;   \/\/ channel zero not used in the example\nconst int walkChannel = 1;\nconst int pickupChannel = 2;\nconst int introChannel = 3;\n\nbool walking;\nbool paused;\nbool intro;\n\nvoid Start() {\n\nAudioPlayer.SetSound(PlayerPrefs.GetInt(\"sound\", 1) != 0);\n\n\/\/\n\/\/ request to load sound effects.  these must be MP3 files placed\n\/\/ in the StreamingAssets folder, directly below Assets folder.\n\/\/\n\nAudioPlayer.Load(\"walk1\");\nAudioPlayer.Load(\"walk2\");\nAudioPlayer.Load(\"walk3\");\nAudioPlayer.Load(\"pickup\");\nAudioPlayer.Load(\"intro\");\n\n\/\/\n\/\/ request various game objects to signal us so we know when\n\/\/ to play or stop playing sound effects\n\/\/\n\nplayer.OnMove += OnPlayerMove;\nplayer.OnPickup += OnPickupCollected;\n\n\/\/\n\/\/ set a flag to play level intro sound effect\n\/\/\n\nintro = true;\n}\n\n\/\/\n\/\/ signal the player is walking or stopping.  player walking is\n\/\/ a state, not a single event, so we set a flag, and let our\n\/\/ Update() method continuously one of three possible effects.\n\/\/\n\/\/ note that the code below works the same whether the signal\n\/\/ is sent every frame, or only when the player state really changes.\n\/\/\n\nvoid OnPlayerMove(bool walkingOrStopped) {\n\n\/\/\n\/\/ walkingOrStopped is true for started walking, false otherwise\n\/\/\n\nif (walkingOrStopped)\nwalking = true;\n\nelse if (walking) {\n\/\/ player stopped walking, stop sound and clear our flag\nwalking = false;\nAudioPlayer.Stop(walkChannel);\n}\n}\n\n\/\/\n\/\/ signal that a pickup was collected.  unlike movement,\n\/\/ this is an event rather than a state change, so we can\n\/\/ play the pickup effect immediately.  the last parameter,\n\/\/ 0.5f, controls the volume -- half volume in this case.\n\/\/\n\nvoid OnPickupCollected() {\n\nAudioPlayer.Play(pickupChannel, \"pickup\", 0.5f);\n}\n\n\/\/\n\/\/ Update method, which we use\n\/\/\n\/\/ - to manage pause\/resume, and sound on\/off\n\/\/\n\/\/ - play level intro sound\n\/\/\n\/\/ - play continuous sound effects (like walking)\n\/\/\n\nvoid Update() {\n\n\/\/\n\/\/ a frame where timeScale is zero, means the game is paused.\n\/\/ if it wasn't already paused, let's pause all sound.\n\/\/\n\nif (Time.timeScale == 0f) {\n\nif (! paused) {\n\npaused = true;\nAudioPlayer.Pause();\n}\n\nreturn;\n}\n\nif (paused) {\n\n\/\/\n\/\/ resuming from pause, and assuming the pause screen\n\/\/ presents a choice to mute all sounds, which is then\n\/\/ stored in PlayerPrefs.\n\/\/\n\/\/ apply the sound on\/off flag to the AudioPlayer object,\n\/\/ then resume all sounds -- which would actually stop all\n\/\/ sounds if the call to SetSound() disabled all sounds.\n\/\/\n\nAudioPlayer.SetSound(PlayerPrefs.GetInt(\"sound\", 1) != 0);\nAudioPlayer.Resume();\npaused = false;\n}\n\n\/\/\n\/\/ if sound is muted, we have nothing further to do\n\/\/\n\nif (! AudioPlayer.GetSound())\nreturn;\n\n\/\/\n\/\/ we need to play level intro sound.  this demonstrates that\n\/\/ sounds are not immediately playable after a call to\n\/\/ AudioPlayer.Load().\n\/\/\n\/\/ we have to keep trying to play the sound, and ask AudioPlayer\n\/\/ if the sound really is playing.  note that this effort is only\n\/\/ necessary for non-recurring sounds which should play soon\n\/\/ after the call to AudioPlayer.Load(), so typically this would\n\/\/ be a level intro sound.\n\/\/\n\nif (intro) {\n\nif (AudioPlayer.IsPlaying(introChannel))\nintro = false;\nelse\nAudioPlayer.Play(introChannel, \"intro\");\n}\n}\n\n\/\/\n\/\/ while the player is walking, which is a continuous state,\n\/\/ we want to keep playing one of several walking sound\n\/\/ effects that we have.\n\/\/\n\nif (walking &amp;&amp; (! AudioPlayer.IsPlaying(walkChannel))) {\n\nint r = Random.Range(1, 3 + 1);\nstring clip = \"walk\" + r.ToString();\nAudioPlayer.Play(walkChannel, clip);\n}\n}\n}\n\n<\/pre>\n<h2>API Reference<\/h2>\n<p>All methods are static and do not require a reference to the GameObject that contains the AudioPlayer component.  They are to be invoked as (for example) <strong>AudioPlayer.Play()<\/strong>, as shown in the example above.<\/p>\n<p><strong>bool GetSound()<\/strong><br \/>\n&#8211; Returns true if sound is enabled, false if sound is muted.<\/p>\n<p><strong>void SetSound(bool sound)<\/strong><br \/>\n&#8211; Enables or mutes sound.   The <strong>Play()<\/strong> method will not begin playing any new sounds while sound is muted.  But note that calling <strong>SetSound(false)<\/strong> does not pause or stop any currently playing sounds.<\/p>\n<p><strong>void Load(string name)<\/strong><br \/>\n&#8211; Makes a request to load an MP3 sound file from the <a title=\"StreamingAssets\" href=\"https:\/\/docs.unity3d.com\/Manual\/StreamingAssets.html\">StreamingAssets<\/a> directory.  This directory should be placed directly below the Assets folder.  The .MP3 extension should not be included in this parameter, only the file name.  Note that this method call returns before the sound is loaded, and the sound may not be immediately available to play.  The example above shows this issue in the context of trying to play a sound at the start of a scene.<\/p>\n<p><strong>void Play(int channel, string name, float volume = 1f, bool loop = false)<\/strong><br \/>\n&#8211; First, this stops the currently playing sound on the specified channel.  Then, if sound is not muted, plays the specified sound on the channel.  Volume should be a value between 0 and 1.  The channel number is a number between 0 and the value set in the <strong>NumChannels<\/strong> property of the <strong>AudioPlayer<\/strong> component.<\/p>\n<p><strong>void Stop(int channel)<\/strong><br \/>\n&#8211; Stops playing the currently playing sound on the specified channel.<\/p>\n<p><strong>bool IsPlaying(int channel)<\/strong><br \/>\n&#8211; Returns true if sound is currently playing on the specified channel.  As noted above, calling <strong>SetSound(false)<\/strong> does not stop sounds that are currently playing, so it is possible for this method to return true even after sound has been muted, while any previously-started sounds have not yet finished playing.<\/p>\n<p><strong>void Pause()<\/strong><br \/>\n&#8211; Pauses all channels that are currently playing.  It is not recommended to issue calls to <strong>Play()<\/strong> after a call to <strong>Pause()<\/strong> and before a matching call to <strong>Resume()<\/strong>.<\/p>\n<p><strong>void Resume()<\/strong><br \/>\n&#8211; Resumes play on all channels that were paused by a previous call to <strong>Pause()<\/strong>.  Note that if <strong>SetSound(false)<\/strong> was called to mute sounds after pausing, then a call to this method will stop, rather than resume, all sounds that were playing prior to calling <strong>Pause()<\/strong>.  It is not recommended to issue calls to <strong>Play()<\/strong> after a call to <strong>Pause()<\/strong> and before a matching call to <strong>Resume()<\/strong>.<\/p>\n<h2>License<\/h2>\n<p>Public domain, free to use however you wish.<\/p>\n<p>THIS SOFTWARE IS PROVIDED BY THE AUTHOR &#8220;AS IS&#8221; AND ANY EXPRESS OR<br \/>\nIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES<br \/>\nOF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.<br \/>\nIN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,<br \/>\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT<br \/>\nNOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,<br \/>\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY<br \/>\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT<br \/>\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF<br \/>\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Many Android devices exhibit varying degrees of latency when playing sound via the Unity AudioSource. This is a well known issue and there are already several solutions out there to work around the problem. This is generally achieved by using the Android SoundPool service. The solution offered here is no different in that regard. The &hellip; <a href=\"https:\/\/www.spaceflint.com\/?p=91\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">AudioPlayer for Unity on Android and iOS<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-91","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=\/wp\/v2\/posts\/91","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=91"}],"version-history":[{"count":20,"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=\/wp\/v2\/posts\/91\/revisions"}],"predecessor-version":[{"id":205,"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=\/wp\/v2\/posts\/91\/revisions\/205"}],"wp:attachment":[{"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=91"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=91"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.spaceflint.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=91"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}