This document is about: FUSION 2

Metaverse WebGL build

Available in the Industries Circle


The Metaverse sample can be build for a WebGL target.

Due to some Unity limitations regarding WebGL builds though, a few point need specific care to work properly and are detailed below.

Note that this WebGL build does not support WebXR (virtual reality in the browser): while it is achievable with some open source libraries, like unity-webxr-export, it is not yet supported by default in Unity, and thus not demonstrated here.

You can test Metaverse WebGL build (Fusion 1) here.

Asynchronous code: Task.Delay

On WebGL builds, some asnyc/await methods do not work as intended, leading to problems. Mainly, the System.Threading.Tasks.Task.Delay method won't work on WebGL builds.

To bypass this limitation, the Metaverse sample provides a WebGL compatible AsyncTask.Task method. It relies on Task.Yield which is compatible for WebGL builds.


public static class AsyncTask
    public static async Task Delay(int milliseconds)
#if !UNITY_WebGL
        await Task.Delay(milliseconds);
        // Unity 2021 do NOT support Task.Delay() in WebGL
        float startTime = Time.realtimeSinceStartup;
        float delay = (float)milliseconds / 1000f;

        while (Time.realtimeSinceStartup - startTime < delay)
            // Wait for the delay time to pass
            await Task.Yield();

AudioSource and Lip synchronization

In current Unity version, for WebGL builds, Audiosource components do not trigger the OnAudioFilterRead callback on sibling components.

Hence, the VoiceDetection component that compute the voice volume using this callback's data cannot work properly. Instead, in WebGL contexts, the VoiceDetection uses instead PhotonVoice callbacks to access raw audio data.

On the user networked prefab, the headset holds a Speaker component, a part of PhotonVoice, which has a RemoveVoice field. Here, it is possible to subscribe to FloatFrameDecoded, to be warned when audio data are received.

Once the speaker is initialized (IsLinked returns true), the OnFloatFrameDecoded callback can be added:


if (speaker == null) speaker = audioSource.GetComponent<Speaker>();
if(HasSubscribedToVoice == false && speaker != null && speaker.IsLinked)
    speaker.RemoteVoice.FloatFrameDecoded += OnFloatFrameDecoded;
    HasSubscribedToVoice = true;

From there, the callback will provide all received audio data, allowing to compute an average voice level, to display lip synchronization accordingly:


private void OnFloatFrameDecoded(FrameOut<float> frame)
    float writeVoiceVolume = 0f;
    foreach (var sample in frame.Buf)
        writeVoiceVolume += Mathf.Abs(sample);
    writeVoiceVolume /= frame.Buf.Length;

    // audioWriteCalls and accumulatedVolume are reset during Update
    accumulatedVolume += writeVoiceVolume;
    voiceVolume = accumulatedVolume / audioWriteCalls;

Note that this adaptation is used for the simple avatar model and ReadyPlayerMe avatars. Regarding ReadyPlayerMe avatars, while on desktop and Quest builds, they rely on Oculus Lipsync, the underlying library was not available for WebGL builds. So, a simplified version is used here : the default RPMLipSync code provided by ReadyPlayerMe is adaptated to use PhotonVoice callbacks.

TextMeshPro materials

In some circumstances, TextMeshPro texts are not visible if a scene contains both TextMeshPro and TextMeshProUGUI components using the exact same font material. To fix this issue, the material associated to a font must be duplicated. Then, make sure that TextMeshPro and TextMeshProUGUI components in your scene do not use the same Material Preset.

Back to top