Why doesn't the sound play when called from a public method?

Hello all,

In my script I’ve created a variable of sound array: private _soundArray : Sound [];

Then from the constructor, I call the method which loads the audio files into the array.
Once player clicked a button, the first element of the sound array needs to play.
I’ve testes playing the audio with autoplay and the audio path is correct.

I’ve first populated the array:

 this._soundArray = [
        new Sound("name1", "./sounds/Teleport1.mp3", scene),
        new Sound("name2", "./sounds/Teleport1.mp3", scene),
        new Sound("name2", "./sounds/Teleport1.mp3", scene)
    ];
    Logger.Warn("sound count is" + this._soundArray.length);

Once player click the button, this method is called:

    public playSoundOnPlayerInput(): void {
    Logger.Warn("first audio is played" + this._soundArray[this._audioIndex].name);
    this._soundArray[this._audioIndex].play();
    this._audioIndex++;
}

The log for the sound count is correct and shows there’s 3 sounds in the array.
In the playSoundOnPlayerInput(), the log for finding the name of the first sound is correct, but the sound would not play and will not give any error in the console.

Going back to the playground prototype, I see that when I create the sound and use the function to play - it works:

    var voice1 = new BABYLON.Sound "TeleportSound", 
    "https://raw.githubusercontent.com....mp3",
    scene, function () 
        {voice1.play(); 
    });

the sound starts playing, but if I don’t use function, and start a new line and simply write voice1.play(); it will not play.

Thanks you

Want to share a playground (or maybe 2?) we the things that is working and the thing that doesn’t ?

Will help us understand the issue better

Hi @RaananW
This is the playground: https://playground.babylonjs.com/#0K3AQ2#335

The audio doesn’t play because it is not yet ready to play. This is why it works when you play it in the onReady callback

I did a simple if/else check on the playground and on my local Type script.
https://playground.babylonjs.com/#0K3AQ2#340

        if (!this._soundArray[1].isReady) {
        Logger.Warn("audio is not ready")
        return;
    } else {
        Logger.Warn("audio is ready to play");
    }

The type script project showed audio is ready, but wouldn’t play, and the playground doesn’t show any thing, and doesn’t play.

How do you usually check if audio is ready tp play?

isReady is a method, not a property.
You should try:

if (!this._soundArray[1].isReady()) {

I thought I saw a pull request somewhere to this effect…

The user has to have interacted with the page for sound (“autoplay policy” they call it), so after the page receives one click you know the audio can be played, so you basically have to bait a click out of the user, then you know it’s ready.

I guess the solution would be to create a recursive method which calls its self until audio is ready and once audio is ready and played once, break the loop.

so I found this method:

scene.registerBeforeRender(() => {
       if (audio.isReady()){
           audio.play();  
        }
     );

As you can expect the audio plays endlessly, so in c# I would in this case either write “break” or “return”, right after the audio.play() method. but doing so in this case doesn’t work.

Would you say its a good solution?

Is there a way to check if sound is ready before it is being played?
I’ve searched google and there’s no example for what I’m looking for.
Usually this mean that the workflow is trivial.
What is your solution for checking if audio is ready to play?

Not sure i got that :slight_smile:

Yes, there is a way - register to the callback, or check if it is ready before you want to play it. Just like anything in javascript, loading is async. Is there a way to know if an image was loaded already? either check when you need it, or register to the onload callback.

I don’t know how to register a callback
How would the code look like? also a reference can help

you did already, in your playground:

Particle System Examples | Babylon.js Playground

I added a comment to let you know where the ready callback is.

But its not what I need.
I need the audio to play, once an external script, such as app.ts is calling a public async method located at the audioController.ts

example of the audioController.cs:

export class AudioController {
public scene: Scene;

private _clappingSound: Sound;

constructor(scene: Scene) {
    this.scene = scene;
}

public async loadSound() {
    this._clappingSound = new Sound("clapping", "./sounds/clapping.wav", this.scene);
    Logger.Log("audio name is " + this._clappingSound.name);
}

// Call this method from app.ts
public async playAudio() {
    //first make sure audio is ready
    if (this._clappingSound.isReady()) {
        this._clappingSound.play();
    }
}

}

if I do what I did in the playground example you’ve attached, the audio plays immediately.

This is an architecture question. More than it is a babylon question.
The sound classes were not “modernized” in the last few releases, so their architecture is not the easiest to wrap your head around, I agree. Hacing said that, the callback is executed at the time the sound is ready. You can wrap it in a promise, and make sure it is resolved before running it. Something like this:

Particle System Examples | Babylon.js Playground

Wrap the constructor with a promise, resolve it with the sound, and you have a promise that is the state of the sound object. You can always do await soundAsync; to get the sound. And it will only load once, of course.

1 Like

The only question remains is how does it looks in TS?

The same, with types? I trust you will manage :slight_smile:

Ok I got it to work.

    public async loadAsync() {
    var applauseAsync = new Promise<Sound>((resolve) => {
        const sound = new Sound(
            "claps",
            "./sounds/clapping.wav ",
            this.scene, () => {
                resolve(sound)
        });

    });

    this._clappingSound = await applauseAsync;
}

public async PlaySound() {
    Logger.Log("sound is ready? " + this._clappingSound.isReady());
    this._clappingSound.play();
}

}

We try to provide help and support for babylon-related questions as much as we can. We love showing examples and hinting in the right direction, but it is hard to provide support to javascript or typescript related questions, as they are more related to understanding the environment in which you are working.

My tip would be - provide a function that returns the promise. this function can take the url you want to load and pass it to the sound constructor. This will allow the scene object (which is the reason for the failure) to be defined after constructing the class.

Writing code is very much related to babylon.js and I don’t know why nowhere in the web I couldn’t find the solution suggested in this question, or I couldn’t interpret the suggested solutions.
I hope this question will help other users.