[SOLVED] How to stop all sounds globally

This is my beautiful project

Music Change Test | Launch

Click the small button on the right that says overlap test then click the change scene button. The sound does not stop playing when the scene changes. That itself is rather weird, but it could be easily fixed with some sort of command that stops all sounds. Something similar to:

window.localStorage.clear();

but for sound. Perhaps something like:

window.sound.stop();

Try this:

this.app.systems.sound.volume = 0;

That just turns off all audio, and setting it to 1 makes the sound come back. I am trying to stop all sound components from playing, not mute the game.

Haven’t tested this yet, but maybe try something like this?

var sounds = this.app.root.findComponents("sound")
sounds.forEach(function(entity){
var slots = entity.sound.slots;
slots.forEach(function(name){
    entity.isPaused(name) = true;
})
})

Looks like it was a deliberate decision to have overlapping sounds be completely independent of the entity and component.

When a sound component is removed (they are removed when the entity is destroyed), if the sound is not overlapping, they are stopped

What you will have to do is to store the sound instance that is returned from the play function in the script and when the script is destroy, call stop on all the instances

Not a massive fan of the design but that’s what you have to do at the moment.

Untested but you get the idea

var PlayButton = pc.createScript('playButton');
PlayButton.attributes.add("songparent", {
    type: "entity",
});
PlayButton.attributes.add("songplayer", {
    type: "entity",
});
PlayButton.prototype.initialize = function () {
    this.entity.element.on('click', this.onClick, this);
    this.soundInstances = [];

    this.on('destroy', function () {
        for (const instance of this.soundInstances) {
            instance.stop();
        }

        this.soundInstances = [];
    }, this)
};
PlayButton.prototype.onClick = function (event) {
    this.songparent.children.forEach(child => {
        child.enabled = false;
    });
    this.songplayer.enabled = true;
    const instance = this.songplayer.sound.play("Slot 1")
    this.soundInstances.push(instance);
};

What I was wanting to do is have a scene that adds a sound at the beginning of a “menu” which is made of multiple scenes. The overlapping audio remaining after the entity is destroyed is actually what makes this possible. The problem is that the script that needs to stop the sound is in a different scene than the scene that has the overlapping sound, so attributes cant be assigned to it.

I tried the script anyway because in this scenario it is attributed, but it gives me:

this.soundInstances is not iterable

Your script doesn’t match mine. This is your script

var PlayButton = pc.createScript('playButton');

PlayButton.attributes.add("songparent", {
    type: "entity",
});

PlayButton.attributes.add("songplayer", {
    type: "entity",
});

PlayButton.prototype.initialize = function() {
    this.entity.element.on('click', this.onClick, this);
    this.on('destroy', function () {
        for (const instance of this.soundInstances) {
            instance.stop();
        }

        this.soundInstances = [];
    }, this)
};

PlayButton.prototype.onClick = function(event) {
    this.songparent.children.forEach(child =>{
        child.enabled = false;
    });
    this.songplayer.enabled = true;
    this.songplayer.sound.play("Slot 1")
};

Oh actually, I misread the engine code a bit. The sound slot already keeps track of all the instances so the project script doesn’t need to track these.

The script can could simply be:

var PlayButton = pc.createScript('playButton');
PlayButton.attributes.add("songparent", {
    type: "entity",
});
PlayButton.attributes.add("songplayer", {
    type: "entity",
});
PlayButton.prototype.initialize = function () {
    this.entity.element.on('click', this.onClick, this);
    
    this.on('destroy', function () {
        this.songplayer.sound.stop("Slot 1")
    }, this)
};
PlayButton.prototype.onClick = function (event) {
    this.songparent.children.forEach(child => {
        child.enabled = false;
    });
    this.songplayer.enabled = true;
    this.songplayer.sound.play("Slot 1")
};

Cannot read properties of undefined (reading ‘stop’)

changing stop to play (which I know works from the other script) gives the same error of Cannot read properties of undefined (reading ‘play’).

Also, this would not even work for my purposes anyway as all sounds must be stopped in a different scene so there would be no attribute entity anyway.

I will try it but that wouldnt work as far as I know, as the entity with the sound is in another destroyed scene so I dont think it would apply.

You were half right. This worked:

var allSounds = this.app.root.findComponents('sound');
allSounds.forEach(function(a){
a.stop();
});
1 Like

Wouldn’t it be better to stop the sound when the scene is destroyed? That is what my code was meant to do.

The error is happening because the entity that is playing the sound is destroyed before the script can handle the callback.

This is the way I would actually do it with that in mind https://playcanvas.com/editor/scene/2316186

var PlayButton = pc.createScript('playButton');
PlayButton.attributes.add("songparent", {
    type: "entity",
});
PlayButton.attributes.add("songplayer", {
    type: "entity",
});
PlayButton.prototype.initialize = function () {
    this.entity.element.on('click', this.onClick, this);

    this.soundComponent = this.songplayer.sound

    this.on('destroy', function () {
        this.soundComponent.stop("Slot 1");
    }, this);
};
PlayButton.prototype.onClick = function (event) {
    this.songparent.children.forEach(child => {
        child.enabled = false;
    });
    this.songplayer.enabled = true;
    this.soundComponent.play("Slot 1")
};

I would cache reference to the sound component and therefore I’m not dependent when the entity that contains the sound component is destroyed.

This will stop all instances of the sounds of the sound components they are referencing when the script type instance is destroyed which is when the scene is destroyed.

Side note: I would look at using SceneRegistry | Engine API Reference - v2.11.0 to change scenes instead. It will even allow you to reload the same scene easily too. See Loading Scenes | PlayCanvas Developer Site for docs

Codeknight’s method is great as a fail safe, it can be costly though as it has to go through every entity in the scene to find the sound components. And then iterate across that array. Both of which can take time in a complex scene.

My purpose for the system is a sound file that is played when a menu loads so there is music across multiple menus. The music would begin once during the second splash screen and continue until the player enters the main menu, opens the level select, selects a level, then plays the level. So the music would stop about 3 scenes removed from when the music first started, so the actual entity would be long gone.

Also, the scenes are very simple. This is the entire scene where the script will occur:

And the spacer isnt even used during runtime. So I dont think it will be a problem

You can also mark this as solved.

1 Like

In which case, I would generally recommend having a global ‘scene’ that loads sub scenes so that you have ‘global’ scene entities that manage functionality that exists across all other scenes (in this case music).

Otherwise you will lose references to SoundInstances if you completely change scenes and will be unable to stop the music from playing once the scene it originates in is destroyed. Codeknight’s solution won’t help in this case as the Sound Component no longer exists in the scene hierarchy.

It has only worked in your example as you are calling it before the scene hierarchy is destroyed.

More on this here Loading Scenes | PlayCanvas Developer Site

I would need to load the scene with the audio at two different points, at the beginning and after returning from a level. I will see what I can do. Actually I can just keep it loaded I think.

Yes but the responsibility of playing the audio can be in the global scene and be driven either through a global API or events that would be sent by the sub scenes loaded

I just load a special scene with only the root that has the audio then destroy and load it whenever I want