FMOD Banks Load in Unity WebGL But Audio Is Silent Until Second Scene Load - How to Fix
Problem: Your Unity WebGL build with FMOD Studio Unity Integration loads cleanly, the FMOD Profiler confirms the bank loaded in the parity test against the Editor, but on the first scene the meters stay flat, no music or SFX plays, and audio only kicks in after the player reloads the page or transitions to a second scene.
Common symptoms reported in 2026 HTML5 demo lanes:
RuntimeManager.LoadBankreturnsOKbutStudio::EventInstance::startproduces no sound.- The Voices and CPU meters in the FMOD Profiler show zero activity on first scene; both wake up after a reload.
- Logs show
FMOD_OKeverywhere, noERR_*codes, and no console errors in the browser DevTools. - Audio works in Editor Play Mode and standalone builds, fails only on the deployed WebGL host.
- Hosted demos pass during local
Build And Runbut fail when uploaded to itch.io, Newgrounds, or your own static host.
This article gives you the fastest safe fix path (gesture unlock + bus suspend check) for ~80% of cases, then walks the four deeper root causes that account for the rest.
Direct answer
In 2026 HTML5 demos, FMOD banks routinely load before the browser AudioContext is unlocked by a user gesture, so Studio::System keeps voices in a suspended state. Audio appears silent until the next scene load happens to coincide with a click. Force an explicit gesture handler, resume the AudioContext, and un-mute the master bus on the first user interaction. If silence persists, validate the bank sample rate against the browser sample rate and serialize Studio::System::init against Unity AudioSettings.
Why this issue spikes in 2026
Three independent shifts in early 2026 combined to push this failure into mainstream reports:
- Browser autoplay policy tightened again - Chromium 122+ and Safari 17.4+ now treat in-page navigation between Unity scenes as insufficient user activation if the original page load did not trigger a gesture. Older builds got an implicit pass; 2026 builds do not.
- FMOD Studio Unity Integration moved to FMOD 2.03.x as default through 2026 - the WebAssembly init path tightened against Unity's own audio subsystem, exposing init-order races that were latent before.
- Unity 6.x WebGL renderer changes the load sequence around scene additive loads; the first scene's
Awake/Startnow runs slightly before the audio thread is ready on some browsers, which the second scene masks because the player has already clicked once by then.
If your project shipped fine in 2025 and broke in 2026, this is almost always the cause.
Root causes (in order of likelihood)
- AudioContext suspended until gesture - The browser holds the WebAudio context in
suspendedstate. FMOD's WebAssembly backend hands voices to the suspended context; meters stay flat. The second scene works because by then the player clicked something. - Master bus mute or snapshot leakage across scene loads - A snapshot or bus mute set in your title scene (often for a fade-in) was not cleared. Bus state persists across
LoadScenebecauseStudio::Systemis global. - Sample rate tier mismatch - The bank was built at 48 kHz but the browser AudioContext defaulted to 44.1 kHz (or vice versa). FMOD resamples internally on most platforms but WebAssembly can stall on the first scene if the resampler initializes lazily.
Studio::System::initordering against UnityAudioSettings- Unity's audio system suspends on focus loss / page hidden; ifRuntimeManagerinitialized before Unity's audio thread was ready (common on Unity 6.x WebGL), the FMOD output device binds to a stub that only flushes on the next audio reset.- Web-incompatible bank contents - A bank built with desktop-only plugins or unsupported codecs silently disables voices on web. Less common but worth checking on a fresh project.
Fastest safe fix path (resolves ~80% of cases)
This sequence handles root causes 1 and 2 - the overwhelming majority of "second scene works" reports.
Step 1 - Add an explicit gesture-unlock bridge
Create a new MonoBehaviour FMODWebAudioUnlock.cs and attach it to a persistent GameObject in your title scene (the one that loads first):
using UnityEngine;
using UnityEngine.UI;
using FMODUnity;
public class FMODWebAudioUnlock : MonoBehaviour
{
[SerializeField] private Button anyButton;
private bool _unlocked;
void Awake()
{
DontDestroyOnLoad(gameObject);
}
void Start()
{
if (anyButton != null)
{
anyButton.onClick.AddListener(UnlockAudio);
}
}
void OnGUI()
{
if (_unlocked) return;
if (Event.current.type == EventType.MouseDown ||
Event.current.type == EventType.TouchDown ||
Event.current.type == EventType.KeyDown)
{
UnlockAudio();
}
}
private void UnlockAudio()
{
if (_unlocked) return;
_unlocked = true;
var system = RuntimeManager.StudioSystem;
system.getCoreSystem(out var core);
core.mixerResume();
if (RuntimeManager.StudioSystem.getBus("bus:/", out var master) == FMOD.RESULT.OK)
{
master.setMute(false);
master.setVolume(1.0f);
}
}
}
Why this works: the OnGUI path catches any mouse/touch/keyboard event, calls mixerResume() on the FMOD core system (which in turn resumes the browser AudioContext), and explicitly un-mutes the master bus in case a snapshot or earlier code left it muted.
Step 2 - Force a gated entry point in your title scene
Add a click-to-start gate before the title scene plays anything. Even one click solves the gesture problem permanently for the rest of the session:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class TitleGate : MonoBehaviour
{
[SerializeField] private Button startButton;
[SerializeField] private string nextScene = "MainMenu";
void Start()
{
startButton.onClick.AddListener(() =>
{
FindFirstObjectByType<FMODWebAudioUnlock>()?.SendMessage("UnlockAudio");
SceneManager.LoadScene(nextScene);
});
}
}
Wire startButton to a "Tap to play" UI element. This pairs the gesture-unlock with the first navigation; audio is ready in the next scene by design rather than by accident.
Step 3 - Clear bus suspend state on every scene load
Add this to a persistent audio manager:
using UnityEngine;
using UnityEngine.SceneManagement;
using FMODUnity;
public class FMODSceneAudioReset : MonoBehaviour
{
void Awake()
{
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (RuntimeManager.StudioSystem.getBus("bus:/", out var master) == FMOD.RESULT.OK)
{
master.setMute(false);
}
}
}
This catches the most common "developer forgot a setMute(true) from a fade-in snapshot" case without forcing you to audit every event in the project.
Verification
Run the first scene in a freshly opened browser tab (Incognito or Private window so you do not inherit a previous session's unlocked AudioContext).
Confirm in this order:
- Manual ear test - First click anywhere on the canvas should produce audible audio within ~200 ms.
- FMOD Profiler attached to the WebGL build (over the network:
RuntimeManager.IsInitialized && enableProfiler == truein your FMOD Settings) - Voices meter rises on the first event, not zero. - Browser DevTools -> Performance -> Audio - The AudioContext state transitions from
suspendedtorunningon first click.
Automated CI verification (recommended once you have one passing run):
[UnityTest]
public IEnumerator FirstSceneAudioPostGestureRmsAboveThreshold()
{
SceneManager.LoadScene("Title");
yield return new WaitForSeconds(0.5f);
var gate = Object.FindFirstObjectByType<TitleGate>();
gate.GetType().GetMethod("SimulateClick").Invoke(gate, null);
yield return new WaitForSeconds(0.5f);
RuntimeManager.StudioSystem.getBus("bus:/", out var master);
master.getCPUUsage(out var exclusive, out var inclusive);
Assert.Greater(inclusive, 0.001f);
}
The 500 ms post-gesture window is enough for any reasonable first SFX or music stem to register CPU activity above the noise floor.
Alternative fixes for edge cases
Edge case 1 - Audio still silent after gesture unlock
If your master bus shows un-muted and the AudioContext is running but voices still produce nothing, you almost certainly hit root cause 3 - sample rate tier mismatch.
- In FMOD Studio, open the bank and check Project Settings -> Built-in Outputs -> WASM sample rate.
- Open the build in DevTools console:
new AudioContext().sampleRatereturns the browser's native rate. - If they disagree (48000 vs 44100 is the common case), either:
- Rebuild banks at the browser-native rate, or
- In Unity, set
AudioSettings.outputSampleRateto match the bank rate beforeRuntimeManagerinitializes.
- Force a reset:
void Awake()
{
var config = AudioSettings.GetConfiguration();
config.sampleRate = 48000;
AudioSettings.Reset(config);
}
Edge case 2 - Studio System init runs before Unity audio is ready
If you instantiate RuntimeManager manually (rare; default plugin handles this), or you call RuntimeManager.StudioSystem very early in Awake, you may get root cause 4.
Serialize the init explicitly:
IEnumerator Start()
{
while (!AudioSettings.GetConfiguration().speakerMode.HasFlag(AudioSpeakerMode.Stereo))
{
yield return null;
}
RuntimeManager.StudioSystem.flushCommands();
}
The wait loop forces Unity's audio thread to come up before any FMOD command is queued. On most projects this is unnecessary, but it resolves the rare "audio never works at all on web" case where the gate-unlock approach above also fails.
Edge case 3 - Web-incompatible plugin in the bank
Open FMOD Studio, switch to the WASM platform target, and inspect each event for plugin warnings (red icons in the event list).
Common offenders:
- Steam Audio (FMOD plugin variant) - desktop only.
- Resonance Audio spatializers - check the WASM compatibility column.
- Custom DSP plugins built as native libraries - not portable to WebAssembly.
Strip these from the web bank specifically. Either:
- Build a dedicated web bank with desktop-only events removed.
- Use platform-specific assets in FMOD Studio so the WASM build automatically excludes incompatible events.
Edge case 4 - itch.io or hosting platform serves wrong MIME
Adjacent issue but easy to mistake for an audio bug. If the host serves .bank files with application/octet-stream and your build relies on streaming bank loading, the load can stall until a memory-mode fallback retries.
Verify with: curl -I https://your-host.example/Build/myproject.wasm
The WASM MIME should be application/wasm. Bank files load fine as application/octet-stream, but COOP/COEP companion headers must be set if you enabled threads.
Prevention
Add this checklist to your WebGL build smoke test so the regression never reaches a public demo again:
- [ ] Title scene contains a gesture gate (click-to-start) before any audio plays.
- [ ] Master bus is explicitly un-muted on every
SceneManager.sceneLoadedevent. - [ ] WebGL build sample rate matches the bank sample rate (most often 48 kHz in 2026).
- [ ] Build target in FMOD Studio is set to WASM for the web bank, with desktop-only plugins stripped.
- [ ] CI run produces a 500 ms post-gesture RMS proof on every PR touching audio.
- [ ] DevTools Performance trace from the deployed URL shows
AudioContextstaterunningwithin 200 ms of first click.
A 30-second smoke harness in CI is worth the time. Once added, you stop shipping silent-first-scene demos to itch.io.
FAQ
Why does the second scene work but not the first?
By the time the player navigates between scenes they have already clicked something (a button, even the canvas). That click satisfies the browser's user-gesture requirement and resumes the AudioContext. The first scene plays before the click, so the same code path produces silence.
Does this affect FMOD 2.02.x as well?
Yes, but it spiked in 2026 because FMOD 2.03.x tightened the WebAssembly init path against Unity's audio subsystem. The fix is the same regardless of FMOD version.
Why does my Editor parity test pass?
Editor Play Mode does not run inside a browser AudioContext. It uses Unity's native audio device directly with no autoplay gate. The parity test confirms the bank is valid; it cannot catch browser-side init gating.
Should I migrate off FMOD for web builds?
No. The fix above is approximately 40 lines of code and resolves the issue permanently. FMOD's WebAssembly target is mature in 2026 for small-to-medium audio designs.
Can I just call AudioContext.resume() from JavaScript?
You can, but you also need to resume FMOD's internal core mixer (core.mixerResume()) and un-mute the master bus. The C# bridge above handles both halves; calling only one is the most common cause of a half-working fix.
Related help articles
- FMOD Banks Not Loading in Unity Addressables Build - How to Fix - when the issue is bank loading itself rather than post-load silence.
- FMOD Studio Bank Version Mismatch in CI Builds - How to Fix - CI-side bank manifest drift that can masquerade as a silent-first-scene issue.
- Godot 4.5 Web Export Audio Silent on First Load - User Gesture Unlock and Bus Init Fix - same browser-side autoplay-gate diagnosis applied to Godot, useful if you maintain both engines.
- Unity WebGL Build Out of Memory at Runtime - WASM Memory and Asset Compression Fix - sometimes silent-first-scene reports are actually memory pressure shutting down the audio thread; check this if RAM headroom is tight.
- Unity 6.6 LTS WebGL WebGPU Fallback Blank Canvas Shader Compile Fix - paired rendering-side WebGL fallback diagnosis for the same Unity 6.x WebGL load sequence shifts.
Further reading
- FMOD documentation: HTML5 Platform Specifics - official WASM target notes.
- FMOD documentation: Studio::System::init reference - confirms init ordering expectations.
- MDN: AudioContext.resume() and autoplay policy - browser-side gesture rules in 2026.
- Unity documentation: AudioSettings.Reset - how to force Unity's audio thread to re-bind output device.
Bookmark this fix for quick reference before your next demo upload. Share this article with your dev friends if they ship FMOD + Unity WebGL - silent-first-scene is the single most common regression report in 2026 indie audio threads, and the fix is one MonoBehaviour and one master-bus reset.