diff --git a/src/AudioService.cs b/src/AudioService.cs
index 0607b0c..4a7b199 100644
--- a/src/AudioService.cs
+++ b/src/AudioService.cs
@@ -52,6 +52,7 @@ namespace Cthangover.Mods.Audio
/// Creates the three audio buses (Music/SFX/Ambient) if missing, the music and ambient players, and applies saved settings.
protected override void OnReady()
{
+ ApplicationExit.Preparing += PrepareExit;
AddToGroup("AudioService");
SetupBuses();
SetupMusicPlayer();
@@ -77,6 +78,39 @@ namespace Cthangover.Mods.Audio
}
}
+ /// Stops and detaches streams while the audio server still exists, including pooled sound players.
+ protected override void OnExitTree()
+ {
+ ApplicationExit.Preparing -= PrepareExit;
+ ReleasePlayer(musicPlayer);
+ ReleasePlayer(ambientPlayer);
+ foreach (var player in soundPlayers.Values) ReleasePlayer(player);
+ soundPlayers.Clear();
+ musicPlayer = null;
+ ambientPlayer = null;
+ }
+
+ private async System.Threading.Tasks.Task PrepareExit(SceneTree tree)
+ {
+ SetProcess(false);
+ ambientPlayer.Finished -= OnAmbientFinished;
+ ReleasePlayer(musicPlayer);
+ ReleasePlayer(ambientPlayer);
+ foreach (var player in soundPlayers.Values) ReleasePlayer(player);
+ // Stop is queued to the mixer. Keep the main loop alive for its fade
+ // and deferred playback destruction before SceneTree.Quit shuts the server down.
+ var deadline = Godot.Time.GetTicksMsec() + 150;
+ while (Godot.Time.GetTicksMsec() < deadline)
+ await ToSignal(tree, SceneTree.SignalName.ProcessFrame);
+ }
+
+ private static void ReleasePlayer(AudioStreamPlayer player)
+ {
+ if (!IsInstanceValid(player)) return;
+ player.Stop();
+ player.Stream = null;
+ }
+
private int lastSoundsVolume = -1;
private int lastMusicsVolume = -1;
private int lastAmbientVolume = -1;
diff --git a/src/MusicPlayerBehaviour.cs b/src/MusicPlayerBehaviour.cs
index 20518a7..06ed7e6 100644
--- a/src/MusicPlayerBehaviour.cs
+++ b/src/MusicPlayerBehaviour.cs
@@ -46,6 +46,33 @@ namespace Cthangover.Mods.Audio
///
public bool IsCanAutoPlay { get; set; } = true;
+ /// Releases the pending fade and player reference when the audio mod leaves the tree.
+ protected override void OnExitTree()
+ {
+ ApplicationExit.Preparing -= PrepareExit;
+ StopForExit();
+ }
+
+ private System.Threading.Tasks.Task PrepareExit(SceneTree tree)
+ {
+ StopForExit();
+ return System.Threading.Tasks.Task.CompletedTask;
+ }
+
+ private void StopForExit()
+ {
+ IsCanAutoPlay = false;
+ SetProcess(false);
+ if (IsInstanceValid(fadeTween)) fadeTween.Kill();
+ fadeTween = null;
+ if (IsInstanceValid(audioPlayer))
+ {
+ audioPlayer.Stop();
+ audioPlayer.Stream = null;
+ }
+ audioPlayer = null;
+ }
+
private Tween fadeTween;
private bool playlistsInited;
private double autoAdvanceCooldown;
@@ -53,6 +80,7 @@ namespace Cthangover.Mods.Audio
/// Registers in the music_player group and locates the AudioService music player.
protected override void OnReady()
{
+ ApplicationExit.Preparing += PrepareExit;
AddToGroup("music_player");
TryFindAudioPlayer();
}