Hey all, I’m posting this to share an ongoing iOS audio issue with FMOD in Unity that’s still unresolved even after upgrading to the latest FMOD version (as of writing).
The Core Issue
On iOS, audio completely stops working after:
- the app is minimized/backgrounded,
- returning from an ad (especially rewarded/interstitial ads),
- or any interruption like phone calls or multitasking.
Once the app regains focus, FMOD doesn’t resume audio, and nothing plays from that point onward — even though no errors are thrown and everything appears normal in Unity.
What I Tried
I originally encountered this issue using FMOD 2.02.05, and after seeing some related bugfixes in changelogs, I upgraded to 2.02.28. Unfortunately, the issue still persists in the new version.
Here’s what I implemented to try and fix it:
- Manually suspending the FMOD mixer on app background/ad start with
RuntimeManager.CoreSystem.mixerSuspend()
- Resuming it later with
RuntimeManager.CoreSystem.mixerResume()
after a small delay
- Tracking mixer state with a
bool isMixerSuspended
flag to prevent double-calling (which can crash on iOS)
- Delaying resume logic using a coroutine to give iOS time to recover
private bool isMixerSuspended = false;
private void HandleAppLostFocus()
{
if (!isMixerSuspended)
{
RuntimeManager.CoreSystem.mixerSuspend();
RuntimeManager.CoreSystem.update();
isMixerSuspended = true;
}
}
private void HandleAppGainedFocus()
{
CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(() =>
{
if (isMixerSuspended)
{
RuntimeManager.CoreSystem.mixerResume();
RuntimeManager.CoreSystem.update();
isMixerSuspended = false;
}
}, 0.2f);
}
However — even with this system in place, FMOD still fails to resume audio after ads or app minimization on iOS. No crashes, but no sound either.
PS:The sound resumes after 10-15 secs
Hi,
Thank you for all the information and the code.
Unfortunately, I am not able to reproduce the issue with a simple test of minimizing and then reopening the app.
Would it be possible to create a stripped out version of your project and upload it to your profile to test on my side? Alternatively, could I get the full script how and when you are calling both these handle functions?
using UnityEngine;
using FMODUnity;
using FMOD.Studio;
using System;
using Controllers;
using NUnit.Framework.Constraints;
using UnityEngine.SceneManagement;
using static BikeData;
public class SoundManager : Singleton<SoundManager>
{
[Header("-----Events-----"), SerializeField]
OnGameStateChanged OnGameStateChangedEvent;
[SerializeField] OnMusicVolumeChanged OnMusicVolumeChangedEvent;
[SerializeField] OnSfxVolumeChanged OnSfxVolumeChangedEvent;
[SerializeField] OnBikeSoundsStop OnBikeSoundsStopEvent;
[SerializeField] OnNotEnoughResources OnNotEnoughResourcesEvent;
[SerializeField] OnFreeRewardClaimed OnFreeRewardClaimedEvent;
[SerializeField] OnRPItemCollected OnRPItemCollectedEvent;
[Header("References")] public StudioEventEmitter menuMusicEmitter;
public StudioEventEmitter gameplayMusicEmitter;
public StudioEventEmitter pauseSnapshot;
public StudioEventEmitter tunnelSnapshot;
public StudioEventEmitter adsSnapshot;
public Bus bgMusicBus, sfxBus, masterBus;
public void PlayBikeRevSound(BikeCategory bikeCategory)
{
switch (bikeCategory)
{
case BikeCategory.Trail:
PlaySoundOneShot("event:/UI/Bike_Rev/Rev_Trail");
break;
case BikeCategory.Special:
PlaySoundOneShot("event:/UI/Bike_Rev/Rev_Special");
break;
case BikeCategory.Sports:
PlaySoundOneShot("event:/UI/Bike_Rev/Rev_Sports");
break;
case BikeCategory.Chopper:
PlaySoundOneShot("event:/UI/Bike_Rev/Rev_Chopper");
break;
default:
break;
}
}
public void PlaySoundOneShot(string eventName)
{
try
{
RuntimeManager.PlayOneShot(eventName);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
private void HandleAppLostFocus()
{
try
{
Debug.Log("🔇 FMOD: Lost focus, suspending mixer...");
RuntimeManager.CoreSystem.mixerSuspend();
RuntimeManager.CoreSystem.update();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
private void HandleAppGainedFocus()
{
CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(() =>
{
try
{
Debug.Log("🔊 FMOD: Gained focus, resuming mixer...");
RuntimeManager.CoreSystem.mixerSuspend();
CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(
() =>
{
try
{
RuntimeManager.CoreSystem.mixerResume();
RuntimeManager.CoreSystem.update();
}
catch (Exception e)
{
Debug.LogException(e);
}
}, 0.4f);
}
catch (Exception e)
{
Debug.LogException(e);
}
}, 0.2f);
}
public void HandleAdStarted()
{
try
{
if (adsSnapshot)
{
adsSnapshot.Play();
}
Debug.Log("📺 FMOD: Ad started, suspending audio...");
// RuntimeManager.CoreSystem.mixerSuspend();
//RuntimeManager.CoreSystem.update();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
public void HandleAdEnded()
{
CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(() =>
{
try
{
if (adsSnapshot)
{
adsSnapshot.Stop();
}
Debug.Log("🔊 FMOD: Gained focus, resuming mixer...");
RuntimeManager.CoreSystem.mixerSuspend();
CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(
() =>
{
try
{
RuntimeManager.CoreSystem.mixerResume();
RuntimeManager.CoreSystem.update();
}
catch (Exception e)
{
Debug.LogException(e);
}
}, 0.4f);
}
catch (Exception e)
{
Debug.LogException(e);
}
}, 0.2f);
}
private void OnDisable()
{
OnGameStateChangedEvent.GameEvent -= HandleOnGameStateChangedEvent;
OnMusicVolumeChangedEvent.GameEvent -= HandleOnMusicVolumeChangedEvent;
OnSfxVolumeChangedEvent.GameEvent -= HandleSFXVolumeChangedEvent;
OnNotEnoughResourcesEvent.GameEvent -= HandleOnNotEnoughResourcesEvent;
OnFreeRewardClaimedEvent.GameEvent -= HandleOnFreeRewardClaimedEvent;
OnRPItemCollectedEvent.GameEvent -= HandleOnRPItemCollectedEvent;
}
private void OnEnable()
{
OnGameStateChangedEvent.GameEvent += HandleOnGameStateChangedEvent;
OnMusicVolumeChangedEvent.GameEvent += HandleOnMusicVolumeChangedEvent;
OnSfxVolumeChangedEvent.GameEvent += HandleSFXVolumeChangedEvent;
OnNotEnoughResourcesEvent.GameEvent += HandleOnNotEnoughResourcesEvent;
OnFreeRewardClaimedEvent.GameEvent += HandleOnFreeRewardClaimedEvent;
OnRPItemCollectedEvent.GameEvent += HandleOnRPItemCollectedEvent;
}
private void HandleOnRPItemCollectedEvent(int arg0, RPButtonType arg1)
{
PlaySoundOneShot(AudioEvents.UI.DAILY_REWARD_CLAIM);
}
private void HandleOnFreeRewardClaimedEvent(int arg0)
{
PlaySoundOneShot(AudioEvents.UI.DAILY_REWARD_CLAIM);
}
private void HandleOnNotEnoughResourcesEvent(int amount, ResourceType resourceType)
{
PlaySoundOneShot("event:/UI/Error");
}
private void Start()
{
SceneManager.activeSceneChanged += HandleSoundOnActiveSceneChanged;
masterBus = RuntimeManager.GetBus("bus:/Master");
bgMusicBus = RuntimeManager.GetBus("bus:/Master/Music");
sfxBus = RuntimeManager.GetBus("bus:/Master/SFX");
CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(() =>
{
OnMusicVolumeChangedEvent.InvokeEvent(DataManager.Instance.GetGameData().MusicVolume);
OnSfxVolumeChangedEvent.InvokeEvent(DataManager.Instance.GetGameData().SFXVolume);
}, 2f);
}
private void HandleSoundOnActiveSceneChanged(Scene arg0, Scene arg1)
{
/*CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(() =>
{
try
{
if (adsSnapshot)
{
adsSnapshot.Stop();
}
Debug.Log("🔊 FMOD: Gained focus, resuming mixer...");
RuntimeManager.CoreSystem.mixerSuspend();
CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(
() =>
{
try
{
RuntimeManager.CoreSystem.mixerResume();
RuntimeManager.CoreSystem.update();
}
catch (Exception e)
{
Debug.LogException(e);
}
}, 0.4f);
}
catch (Exception e)
{
Debug.LogException(e);
}
}, 0.2f);*/
}
private void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
HandleAppGainedFocus();
}
else
{
HandleAppLostFocus();
}
}
private void HandleOnGameStateChangedEvent(GameState gamestate)
{
if (adsSnapshot)
adsSnapshot.Stop();
switch (gamestate)
{
case GameState.GamePlay:
menuMusicEmitter.Stop();
pauseSnapshot.Stop();
//gameplayMusicEmitter.Play();
break;
case GameState.Paused:
PlaySoundOneShot("event:/UI/Pause_Menu");
/*pauseSnapshot.Play();*/
//gameplayMusicEmitter.Stop();
break;
case GameState.PreComplete:
PlaySoundOneShot("event:/VO/Victory_Mission");
PlaySoundOneShot("event:/UI/Level_Finish");
//gameplayMusicEmitter.Stop();
break;
case GameState.PreFail:
// gameplayMusicEmitter.Stop();
CoroutineRunner.Instance.WaitForRealTimeDelayAndExecute(
() => PlaySoundOneShot("event:/VO/Failed_Mission"), 1f);
;
OnBikeSoundsStopEvent.InvokeEvent();
break;
case GameState.LevelComplete:
// PlaySoundOneShot("event:/UI/Confetti");
break;
case GameState.LevelFail:
// gameplayMusicEmitter.Stop();
break;
case GameState.MainMenu:
pauseSnapshot.Stop();
//gameplayMusicEmitter.Stop();
//menuMusicEmitter.Play();
PlaySoundOneShot("event:/UI/Menu_Return");
if (GameManager.Instance.playerBike)
OnBikeSoundsStopEvent.InvokeEvent();
break;
case GameState.Shop:
PlaySoundOneShot("event:/UI/Shop_Enter");
break;
case GameState.Settings:
PlaySoundOneShot("event:/UI/Settings_Enter");
break;
case GameState.BikeSelection:
PlaySoundOneShot("event:/UI/Showroom_Enter");
break;
case GameState.ModeSelection:
PlaySoundOneShot("event:/UI/StageSelect_Enter");
break;
case GameState.Respawn:
//RuntimeManager.PlayOneShot("event:/UI/Counter_Pass");
break;
case GameState.LevelSelection:
PlaySoundOneShot("event:/UI/event:/UI/Level_Selection");
break;
case GameState.RPPass:
PlaySoundOneShot("event:/UI/RP_Panel");
break;
case GameState.None:
break;
case GameState.Loading:
OnBikeSoundsStopEvent.InvokeEvent();
break;
case GameState.AdsLoading:
break;
case GameState.DailyReward:
PlaySoundOneShot("event:/UI/DR_Panel");
break;
case GameState.Quit:
PlaySoundOneShot("event:/UI/Exit_Game");
break;
case GameState.Home:
break;
case GameState.Profile:
break;
case GameState.Challenges:
break;
case GameState.EnvironmentSelection:
PlaySoundOneShot("event:/UI/StageSelect_Enter");
break;
case GameState.FreeCoins:
break;
case GameState.Splash:
break;
case GameState.PreSplash:
break;
case GameState.TapToPlay:
break;
case GameState.CharacterSelection:
PlaySoundOneShot("event:/UI/Character_Selection");
break;
case GameState.Multiplayer:
break;
case GameState.PoliceChase:
break;
case GameState.Boss:
break;
case GameState.SubMode:
PlaySoundOneShot("event:/UI/Career_Sub");
break;
case GameState.CutScene:
menuMusicEmitter.Stop();
pauseSnapshot.Stop();
break;
default:
break;
}
}
private void HandleSFXVolumeChangedEvent(float value)
{
try
{
sfxBus.setVolume(value);
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
private void HandleOnMusicVolumeChangedEvent(float value)
{
try
{
bgMusicBus.setVolume(value);
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
private void OnDestroy()
{
SceneManager.activeSceneChanged -= HandleSoundOnActiveSceneChanged;
}
}
public static class AudioEvents
{
public static class UI
{
public const string DAILY_REWARD_CLAIM = "event:/UI/DR_Claim";
public const string TEXT_COUNTER = "event:/UI/Text_Counter";
public const string CONFETTI = "event:/UI/Confetti";
public const string LEVEL_FINISH = "event:/UI/Level_Finish";
public const string COUNTER_PASS = "event:/UI/Counter_Pass";
public const string CUSTOMIZATION_APPLY = "event:/UI/Customization_Apply";
public const string CLOTHES_CHANGE = "event:/UI/Clothes_Change";
public const string MAP_OVERLAY = "event:/UI/Map_Overlay";
public const string GIFT_PANEL = "event:/UI/Gift_Panel";
public const string LEVEL_COMPLETE = "event:/UI/Result_Panel";
public const string ACKNOWLEDGEMENT_PANEL = "event:/UI/Reward_Unlocked";
public const string PANEL_FADE = "event:/UI/Panel_Fade";
public const string CAMERA_SHUTTER = "event:/UI/Camera_Shutter";
public const string WRAP_APPLY = "event:/UI/Wrap_Apply";
public const string KEYCHAIN_APPLY = "event:/UI/Keychain_Apply";
public const string BUTTON_BACK = "event:/UI/Click_Back";
public const string BUTTON_FORWARD = "event:/UI/Click_Forward";
public const string COINS_COLLECTED = "event:/UI/Coins_Collected";
}
public static class Gameplay
{
public const string FUEL_PICKUP = "event:/Gameplay/Pickup/Fuel";
public const string POLICE_SIREN = "event:/Ambience/Traffic_Engine/Police_Car";
public const string WHEELIE_START = "event:/Gameplay/Wheelie";
public const string WHEELIE_STOP = "event:/Gameplay/Bike_Body";
public const string ONE = "event:/VO/Countdown/One";
public const string TWO = "event:/VO/Countdown/Two";
public const string THREE = "event:/VO/Countdown/Three";
}
}
Here is the full script above that i shared
To be more specific AD Started and Ended is called upon each ad open and ad close or failed callback
1 Like
Thank you for the code, all looks good there.
There was a similar issue where the buffer size is being changed when the app looses focus and then not being reset when handed back to FMOD which is causing issues. More information and a possible solution can be found here: Event callbacks don't work if other audio is playing on iOS - #4 by Leah_FMOD.
Hope this helps!
Thanks for sharing i will implement this fix and get back with the results
1 Like
Hey @Connor_FMOD
it still did not worked
can u please verify one thing to me
After updating the FMOD and its banks to the latest verified version and using default package without doing any additional tweaks solve my problem
can u please share the version in which u have fixed this issue?
Hi,
Thanks for testing that!
I’m sorry, I don’t quite follow the issue here?
This task is still in our backlog, but I have noted your interest.