No sound in my little XR test?

Hello,

I am trying to create some basic XR program. It works by entering VR, having a groundplane and some spawned cubes and when pressing the trigger on right controller, then it spawns/creates another cube.

Very simple, but now comes my problem.

I want to play a sound when I press that trigger/create a new cube. The sound is in shoot.mp3 and that file is in the same directory as index.html. Everything is served over https.

I don’t get sound. Never mind the title saying (Local WAV. It is mp3).

Is there anybody who would be so kind to give my code a quick look ? It is very basic code, but maybe you discover why the sound doesn’t play on my quest 3?

I would be so gratefull.

Thank you in advance,

Bart (Belgium)

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Babylon.js WebXR Gunshot Demo</title>
    <style>
        html,
        body {
            overflow: hidden;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        canvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>

    <!-- Babylon.js CDN -->
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
    <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>
    <script src="https://cdn.babylonjs.com/webxr/babylon.webxr.min.js"></script>

    <script>
        const canvas = document.getElementById("renderCanvas");
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = async () => {
            const scene = new BABYLON.Scene(engine);
            scene.clearColor = new BABYLON.Color3(0.8, 0.9, 1.0);
            const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

            // Create ground plane (10x10 meters)
            const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 10, height: 10 }, scene);
            const groundMat = new BABYLON.StandardMaterial("groundMat", scene);
            groundMat.diffuseColor = new BABYLON.Color3(0.5, 0.5, 0.5);
            ground.material = groundMat;

            // Add 3 random cubes on the ground
            for (let i = 0; i < 3; i++) {
                const box = BABYLON.MeshBuilder.CreateBox("box" + i, { size: 0.3 }, scene);
                box.position = new BABYLON.Vector3(
                    (Math.random() - 0.5) * 9,
                    0.15,
                    (Math.random() - 0.5) * 9
                );
                const mat = new BABYLON.StandardMaterial("mat" + i, scene);
                mat.diffuseColor = new BABYLON.Color3(Math.random(), Math.random(), Math.random());
                box.material = mat;
            }

            // 🔊 Load local shoot.mp3 (same folder as HTML file)
            const gunshotSound = new BABYLON.Sound("shoot", "shoot.mp3", scene, () => {
                console.log("shoot.mp3 is loaded and ready!");
            }, {
                volume: 1.0,
                spatialSound: true,
                autoplay: false,
                loop: false
            });

            // Enable XR
            const xr = await scene.createDefaultXRExperienceAsync({
                floorMeshes: [ground]
            });

            // Attach to controllers
            xr.input.onControllerAddedObservable.add(controller => {
                controller.onMotionControllerInitObservable.add(mc => {
                    controller.pointer.isVisible = true;

                    // LEFT controller: red sphere
                    if (controller.inputSource.handedness === "left") {
                        const redSphere = BABYLON.MeshBuilder.CreateSphere("redSphere", { diameter: 0.1 }, scene);
                        const redMat = new BABYLON.StandardMaterial("redMat", scene);
                        redMat.diffuseColor = new BABYLON.Color3(1, 0, 0);
                        redSphere.material = redMat;
                        redSphere.parent = controller.grip || controller.pointer;
                    }

                    // RIGHT controller: blue cube + trigger + sound
                    if (controller.inputSource.handedness === "right") {
                        const blueCube = BABYLON.MeshBuilder.CreateBox("blueCube", { size: 0.1 }, scene);
                        const blueMat = new BABYLON.StandardMaterial("blueMat", scene);
                        blueMat.diffuseColor = new BABYLON.Color3(0, 0, 1);
                        blueCube.material = blueMat;
                        blueCube.parent = controller.grip || controller.pointer;

                        const trigger = mc.getComponent("xr-standard-trigger");
                        if (trigger) {
                            let wasPressed = false;

                            trigger.onButtonStateChangedObservable.add(() => {
                                const pressure = trigger.value;

                                // One-time fire logic
                                if (pressure > 0.5 && !wasPressed) {
                                    wasPressed = true;
                                    console.log("fire");

                                    // Spawn new 50cm cube
                                    const newCube = BABYLON.MeshBuilder.CreateBox("firedCube", { size: 0.5 }, scene);
                                    newCube.position = new BABYLON.Vector3(
                                        (Math.random() - 0.5) * 9,
                                        0.25,
                                        (Math.random() - 0.5) * 9
                                    );
                                    const newMat = new BABYLON.StandardMaterial("firedMat", scene);
                                    newMat.diffuseColor = new BABYLON.Color3(Math.random(), Math.random(), Math.random());
                                    newCube.material = newMat;

                                    // Play gunshot sound at controller's position
                                    if (gunshotSound.isReady()) {
                                        gunshotSound.setPosition(controller.pointer.position);
                                        gunshotSound.play();
                                    } else {
                                        console.warn("Sound not ready yet!");
                                    }
                                }

                                // Reset trigger press state
                                if (pressure < 0.2 && wasPressed) {
                                    wasPressed = false;
                                }
                            });
                        }
                    }
                });
            });

            return scene;
        };

        createScene().then(scene => {
            engine.runRenderLoop(() => {
                scene.render();
            });
        });

        window.addEventListener("resize", () => {
            engine.resize();
        });
    </script>
</body>

</html>

cc @docEdub

I did some extra tests, since I discovered (yes sorry) that there is a new audioengine and the old one is still there but not loaded by default.

I tried to programatically (with parameter) enable the old engine again and then write some kind of unlock code to enable sound.

What I discovered is that my code at Babylon.js WebXR Gunshot Demo now starts in Quest3 browser by showing a muted sound icon in the upper left corner of the browser and as usual the “enter VR” icon in the lower right.

  1. When I enter VR without clicking on the “sound muted” icons , my little program works and I can spawn cubes by pressing trigger on the right controller, but no sound

  2. When I enter VR bu have first clicked on the “sound muted” icon, then I enter my vr world, can spawn cubes and I get sound playing.

So somehow I still must be doing something wrong? Or am I mixing code from old and new engine?

It seems so strange that you would have to click that “sound muted” icon in upper left to enable sound with user interaction of you allready interact by clicking the “enter vr” icon on the lower right?

Is there somebody who could explain what is wrong?

I also saw that @Deltakosh David mentioned @docEdub Andy who seems to have made the new soundsystem?

Maybe I do something terribly wrong.

My purpose is to make small educational webapps for primary school children to use. I come from a background in Unity XR dev, but would prefer not to have to build full apps and put those on a store for people to be able to use them. Playing inside a webpage would be so nice for this purpose, so I still have lots of little steps to learn, but at this moment struggle a little with basic sound.

All help would be extremely appreciated

Ya, the audio engine should unlock when you press the “enter vr” button. I’ll investigate.

Oh thank you soo much. I would love to get this working. Maybe if you could have some working playground that demonstrates this? (I have to admit that I might have mixed code and you will probably create is with audio engine v2, which would be superb!)

I made a quick playground to try and reproduce the issue you’re describing, but it works correctly when entering VR

https://playground.babylonjs.com/#CYV86H

When the “enter vr” button is pressed, the audio engine unlocks and the sound plays as exepected.

Can you make a playground that shows the issue you are running into?

Hi @docEdub ,

If I look at the code I wrote and compare that to what you wrote. Then the only reason why it didn’t work for me is because my code seems to be completely wrong. Thanks for the playground which indeed works perfectly fine.

I am stil learning a lot and when searching for answers I also used chatgpt. And while explicitly asked to use audioengine v2 , it looks like my code is very very v1.

Sorry for that. I am very happy that this works now.

I do have 1 little question.

You use const gunshot = await BABYLON.CreateSoundAsync(“gunshot”, “https://playground.babylonjs.com/sounds/gunshot.wav”); to load a sound.

But suppose that I want to load 10 sounds and they are quite large (5MB / each), then I guess this will take some time to load. I would like to preload everything.

I don’t know….. maybe preload and then enter VR, or maybe enter VR and there preload, but are there techniques for that in Babylon? (same would be needed with meshed).

When you do everything async then eventually it will be loaded, but I would like to be able to preload (with some kind of progress meter) meshes and sounds and then only start the app when everything is loaded.

Kind regards,

Bart

Good, thanks for letting me know :+1:

To preload assets before starting the app, take a look at the Babylon.js custom loading screen API:

https://doc.babylonjs.com/features/featuresDeepDive/scene/customLoadingScreen