From 7630872229f70b42786500171222061082e2e3fd Mon Sep 17 00:00:00 2001
From: DaBultz <27208227+DaBultz@users.noreply.github.com>
Date: Sat, 2 May 2026 11:41:10 +0200
Subject: [PATCH 1/8] replace NAudio with SoundFlow
# Conflicts:
# OofPlugin/OofPlugin.csproj
---
OofPlugin/OofPlugin.csproj | 4 +-
OofPlugin/SoundManager.cs | 110 ++++++++++++++++-------------------
OofPlugin/packages.lock.json | 17 ++----
3 files changed, 55 insertions(+), 76 deletions(-)
diff --git a/OofPlugin/OofPlugin.csproj b/OofPlugin/OofPlugin.csproj
index 7dd73a6..cb8a463 100644
--- a/OofPlugin/OofPlugin.csproj
+++ b/OofPlugin/OofPlugin.csproj
@@ -23,8 +23,8 @@
-
-
+
+
diff --git a/OofPlugin/SoundManager.cs b/OofPlugin/SoundManager.cs
index 80bbe1b..ceb08ea 100644
--- a/OofPlugin/SoundManager.cs
+++ b/OofPlugin/SoundManager.cs
@@ -1,9 +1,15 @@
-using NAudio.Wave;
+using SoundFlow.Abstracts.Devices;
+using SoundFlow.Backends.MiniAudio;
+using SoundFlow.Components;
+using SoundFlow.Providers;
using System;
using System.IO;
+using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
+using AudioFormat = SoundFlow.Structs.AudioFormat;
+using PlaybackState = SoundFlow.Enums.PlaybackState;
namespace OofPlugin;
@@ -12,9 +18,13 @@ internal class SoundManager : IDisposable {
private readonly DeadPlayersList DeadPlayersList;
// sound
- public bool isSoundPlaying { get; private set; } = false;
- private DirectSoundOut? soundOut;
- private string? soundFile;
+ private string soundFile;
+
+ private MiniAudioEngine engine;
+ private AudioPlaybackDevice playbackDevice;
+ private AudioFormat audioFormat;
+ private StreamDataProvider dataProvider;
+ private SoundPlayer player;
internal CancellationTokenSource CancelToken;
@@ -22,17 +32,30 @@ public SoundManager(OofPlugin plugin) {
Configuration = plugin.Configuration;
DeadPlayersList = plugin.DeadPlayersList;
+ engine = new MiniAudioEngine();
+ engine.UpdateAudioDevicesInfo();
+
+ // Get the system default playback device
+ var defaultDevice = engine.PlaybackDevices.FirstOrDefault(x => x.IsDefault);
+ audioFormat = AudioFormat.DvdHq;
+ playbackDevice = engine.InitializePlaybackDevice(defaultDevice, audioFormat);
+
LoadFile();
+ dataProvider = new StreamDataProvider(engine, audioFormat, File.OpenRead(soundFile));
+ player = new SoundPlayer(engine, audioFormat, dataProvider);
+ player.PlaybackEnded += (_, _) => player.Stop();
+
+ playbackDevice.MasterMixer.AddComponent(player);
+ playbackDevice.Start();
+
CancelToken = new CancellationTokenSource();
Task.Run(() => OofAudioPolling(CancelToken.Token));
}
public void LoadFile() {
if (string.IsNullOrEmpty(Configuration.DefaultSoundImportPath)) {
- soundFile = Path.Combine(
- Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName,
- "oof.wav");
+ soundFile = Path.Combine(Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName, "oof.wav");
return;
}
@@ -40,54 +63,19 @@ public void LoadFile() {
}
public void Stop() {
- soundOut?.Pause();
- soundOut?.Dispose();
- soundOut = null;
+ player.Stop();
}
public void Play(CancellationToken token, float volume = 1f) {
_ = Task.Run(() => {
- isSoundPlaying = true;
-
- WaveStream reader;
- try {
- reader = new MediaFoundationReader(soundFile);
- }
- catch (Exception ex) {
- isSoundPlaying = false;
- Dalamud.Log.Error("Failed to read sound file", ex);
- return;
+ // To play a sound, we need to stop it first if it's already playing
+ if (player.State == PlaybackState.Playing) {
+ player.Stop();
}
- var audioStream =
- new WaveChannel32(reader) {
- Volume = Configuration.Volume * volume,
- PadWithZeroes = false // you need this or else playbackstopped event will not fire
- };
-
- using (reader) {
- soundOut?.Pause();
- soundOut?.Dispose();
- soundOut = new DirectSoundOut();
-
- try {
- soundOut.Init(audioStream);
- soundOut.Play();
-
- soundOut.PlaybackStopped += (_, _) => { isSoundPlaying = false; };
- }
- catch (Exception ex) {
- isSoundPlaying = false;
- Dalamud.Log.Error("Failed to play sound", ex);
- }
- }
- }, token).ContinueWith((t) => {
- t.Exception?.Handle((e) => {
- Dalamud.Log.Error($"Failed to dispose DirectSoundOut {e} ");
-
- return true;
- });
- });
+ player.Volume = volume;
+ player.Play();
+ }, token);
}
private async Task OofAudioPolling(CancellationToken token) {
@@ -100,8 +88,7 @@ private async Task OofAudioPolling(CancellationToken token) {
// Run on framework thread AND await it so exceptions are observed
await Dalamud.Framework.RunOnFrameworkThread(() => {
- var localPlayer =
- Dalamud.ObjectTable.LocalPlayer;
+ var localPlayer = Dalamud.ObjectTable.LocalPlayer;
if (localPlayer is null)
return;
@@ -111,10 +98,8 @@ await Dalamud.Framework.RunOnFrameworkThread(() => {
float volume = 1f;
- if (Configuration.DistanceBasedOof &&
- player.Distance != Vector3.Zero) {
- var dist =
- Vector3.Distance(localPlayer.Position, player.Distance);
+ if (Configuration.DistanceBasedOof && player.Distance != Vector3.Zero) {
+ var dist = Vector3.Distance(localPlayer.Position, player.Distance);
volume = VolumeFromDist(dist);
}
@@ -139,8 +124,8 @@ public float VolumeFromDist(float dist, float distMax = 30f) {
dist = Math.Min(dist, distMax);
var falloff = Configuration.DistanceFalloff > 0
- ? 3f - Configuration.DistanceFalloff * 3f
- : 2.999f;
+ ? 3f - Configuration.DistanceFalloff * 3f
+ : 2.999f;
var vol = 1f - ((dist / distMax) * (1f / falloff));
return Math.Max(Configuration.DistanceMinVolume, vol);
@@ -164,8 +149,11 @@ async Task PlayTest(float volume) {
public void Dispose() {
CancelToken.Cancel();
CancelToken.Dispose();
-
- soundOut?.Dispose();
- soundOut = null;
+
+ playbackDevice.MasterMixer.RemoveComponent(player);
+ player.Dispose();
+ dataProvider.Dispose();
+ playbackDevice.Dispose();
+ engine.Dispose();
}
-}
+}
\ No newline at end of file
diff --git a/OofPlugin/packages.lock.json b/OofPlugin/packages.lock.json
index 725b17e..5797274 100644
--- a/OofPlugin/packages.lock.json
+++ b/OofPlugin/packages.lock.json
@@ -14,20 +14,11 @@
"resolved": "1.2.39",
"contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
},
- "NAudio.Core": {
+ "SoundFlow": {
"type": "Direct",
- "requested": "[2.2.1, )",
- "resolved": "2.2.1",
- "contentHash": "GgkdP6K/7FqXFo7uHvoqGZTJvW4z8g2IffhOO4JHaLzKCdDOUEzVKtveoZkCuUX8eV2HAINqi7VFqlFndrnz/g=="
- },
- "NAudio.Wasapi": {
- "type": "Direct",
- "requested": "[2.2.1, )",
- "resolved": "2.2.1",
- "contentHash": "lFfXoqacZZe0WqNChJgGYI+XV/n/61LzPHT3C1CJp4khoxeo2sziyX5wzNYWeCMNbsWxFvT3b3iXeY1UYjBhZw==",
- "dependencies": {
- "NAudio.Core": "2.2.1"
- }
+ "requested": "[1.4.0, )",
+ "resolved": "1.4.0",
+ "contentHash": "PVc8NEpwmx16qJdOGRvc7Kpvs/3hNZBuldErmnMQSh9IClAA2UWrafcUIQUxki2GrWB2g71hPSDIUKEo3nRgeA=="
}
}
}
From 8b2f84963f24716335d97f6d7b7aee2e4e9be03a Mon Sep 17 00:00:00 2001
From: DaBultz <27208227+DaBultz@users.noreply.github.com>
Date: Sat, 2 May 2026 15:38:48 +0200
Subject: [PATCH 2/8] custom audio file now works again
---
OofPlugin/SoundManager.cs | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/OofPlugin/SoundManager.cs b/OofPlugin/SoundManager.cs
index ceb08ea..2cbdd57 100644
--- a/OofPlugin/SoundManager.cs
+++ b/OofPlugin/SoundManager.cs
@@ -41,13 +41,6 @@ public SoundManager(OofPlugin plugin) {
playbackDevice = engine.InitializePlaybackDevice(defaultDevice, audioFormat);
LoadFile();
- dataProvider = new StreamDataProvider(engine, audioFormat, File.OpenRead(soundFile));
- player = new SoundPlayer(engine, audioFormat, dataProvider);
-
- player.PlaybackEnded += (_, _) => player.Stop();
-
- playbackDevice.MasterMixer.AddComponent(player);
- playbackDevice.Start();
CancelToken = new CancellationTokenSource();
Task.Run(() => OofAudioPolling(CancelToken.Token));
@@ -68,6 +61,18 @@ public void Stop() {
public void Play(CancellationToken token, float volume = 1f) {
_ = Task.Run(() => {
+ dataProvider = new StreamDataProvider(engine, audioFormat, File.OpenRead(soundFile));
+ player = new SoundPlayer(engine, audioFormat, dataProvider);
+ playbackDevice.MasterMixer.AddComponent(player);
+ playbackDevice.Start();
+
+ player.PlaybackEnded += (_, _) => {
+ player.Stop();
+ playbackDevice.MasterMixer.RemoveComponent(player);
+ player.Dispose();
+ };
+
+
// To play a sound, we need to stop it first if it's already playing
if (player.State == PlaybackState.Playing) {
player.Stop();
From b26dcfc26328b87b03cd494fdbc619ca9f3ac118 Mon Sep 17 00:00:00 2001
From: DaBultz <27208227+DaBultz@users.noreply.github.com>
Date: Sat, 2 May 2026 15:39:55 +0200
Subject: [PATCH 3/8] allow audio to overlap
The audio can now overlap if multiple people die in quick succession
---
OofPlugin/SoundManager.cs | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/OofPlugin/SoundManager.cs b/OofPlugin/SoundManager.cs
index 2cbdd57..9be0cda 100644
--- a/OofPlugin/SoundManager.cs
+++ b/OofPlugin/SoundManager.cs
@@ -23,8 +23,7 @@ internal class SoundManager : IDisposable {
private MiniAudioEngine engine;
private AudioPlaybackDevice playbackDevice;
private AudioFormat audioFormat;
- private StreamDataProvider dataProvider;
- private SoundPlayer player;
+
internal CancellationTokenSource CancelToken;
@@ -56,28 +55,29 @@ public void LoadFile() {
}
public void Stop() {
- player.Stop();
+ // When an audio plays this will cause a tiny lag spike, but as this is only used in the ConfigWindow, it
+ // should be fine
+ playbackDevice.Stop();
}
public void Play(CancellationToken token, float volume = 1f) {
_ = Task.Run(() => {
- dataProvider = new StreamDataProvider(engine, audioFormat, File.OpenRead(soundFile));
- player = new SoundPlayer(engine, audioFormat, dataProvider);
+ var dataProvider = new StreamDataProvider(engine, audioFormat, File.OpenRead(soundFile));
+ var player = new SoundPlayer(engine, audioFormat, dataProvider);
playbackDevice.MasterMixer.AddComponent(player);
playbackDevice.Start();
+ // this cleans up after the playback ends
player.PlaybackEnded += (_, _) => {
player.Stop();
playbackDevice.MasterMixer.RemoveComponent(player);
player.Dispose();
};
-
- // To play a sound, we need to stop it first if it's already playing
if (player.State == PlaybackState.Playing) {
player.Stop();
}
-
+
player.Volume = volume;
player.Play();
}, token);
@@ -155,9 +155,6 @@ public void Dispose() {
CancelToken.Cancel();
CancelToken.Dispose();
- playbackDevice.MasterMixer.RemoveComponent(player);
- player.Dispose();
- dataProvider.Dispose();
playbackDevice.Dispose();
engine.Dispose();
}
From 41c974d7f0b8cc7a6f0600f8110e453dfc8a3927 Mon Sep 17 00:00:00 2001
From: DaBultz <27208227+DaBultz@users.noreply.github.com>
Date: Sun, 3 May 2026 17:29:52 +0200
Subject: [PATCH 4/8] resolve rebase conflcit
---
OofPlugin/OofPlugin.csproj | 1 -
1 file changed, 1 deletion(-)
diff --git a/OofPlugin/OofPlugin.csproj b/OofPlugin/OofPlugin.csproj
index cb8a463..4868a88 100644
--- a/OofPlugin/OofPlugin.csproj
+++ b/OofPlugin/OofPlugin.csproj
@@ -23,7 +23,6 @@
-
From fec6598fd900c77fa7c983a16ef435a1e98ab7a8 Mon Sep 17 00:00:00 2001
From: DaBultz <27208227+DaBultz@users.noreply.github.com>
Date: Mon, 4 May 2026 13:58:47 +0200
Subject: [PATCH 5/8] audio overlapping can be toggled
---
OofPlugin/Configuration.cs | 1 +
OofPlugin/SoundManager.cs | 15 ++++++++++++---
OofPlugin/Windows/ConfigWindow.cs | 8 ++++++++
3 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/OofPlugin/Configuration.cs b/OofPlugin/Configuration.cs
index 25fd5bf..2fb4e61 100644
--- a/OofPlugin/Configuration.cs
+++ b/OofPlugin/Configuration.cs
@@ -26,6 +26,7 @@ public class Configuration : IPluginConfiguration {
//audio settings
public float Volume { get; set; } = 0.5f;
public string DefaultSoundImportPath { get; set; } = string.Empty;
+ public bool AudioOverlap { get; set; } = false;
// the below exist just to make saving less cumbersome
diff --git a/OofPlugin/SoundManager.cs b/OofPlugin/SoundManager.cs
index 9be0cda..53076ba 100644
--- a/OofPlugin/SoundManager.cs
+++ b/OofPlugin/SoundManager.cs
@@ -23,6 +23,7 @@ internal class SoundManager : IDisposable {
private MiniAudioEngine engine;
private AudioPlaybackDevice playbackDevice;
private AudioFormat audioFormat;
+ private SoundPlayer? currentPlayer;
internal CancellationTokenSource CancelToken;
@@ -36,6 +37,7 @@ public SoundManager(OofPlugin plugin) {
// Get the system default playback device
var defaultDevice = engine.PlaybackDevices.FirstOrDefault(x => x.IsDefault);
+
audioFormat = AudioFormat.DvdHq;
playbackDevice = engine.InitializePlaybackDevice(defaultDevice, audioFormat);
@@ -62,8 +64,18 @@ public void Stop() {
public void Play(CancellationToken token, float volume = 1f) {
_ = Task.Run(() => {
+ if (!Configuration.AudioOverlap && currentPlayer != null) {
+ currentPlayer.Stop();
+ playbackDevice.MasterMixer.RemoveComponent(currentPlayer);
+ currentPlayer.Dispose();
+ currentPlayer = null;
+ }
+
var dataProvider = new StreamDataProvider(engine, audioFormat, File.OpenRead(soundFile));
var player = new SoundPlayer(engine, audioFormat, dataProvider);
+
+ currentPlayer = player;
+
playbackDevice.MasterMixer.AddComponent(player);
playbackDevice.Start();
@@ -74,9 +86,6 @@ public void Play(CancellationToken token, float volume = 1f) {
player.Dispose();
};
- if (player.State == PlaybackState.Playing) {
- player.Stop();
- }
player.Volume = volume;
player.Play();
diff --git a/OofPlugin/Windows/ConfigWindow.cs b/OofPlugin/Windows/ConfigWindow.cs
index 768234c..19899a2 100644
--- a/OofPlugin/Windows/ConfigWindow.cs
+++ b/OofPlugin/Windows/ConfigWindow.cs
@@ -59,6 +59,14 @@ public override void Draw() {
ImGui.Spacing();
+ ImGui.TextColoredWrapped(headingColor, "Settings");
+ var audioOverlap = Configuration.AudioOverlap;
+ if (ImGui.Checkbox("Allow audio overlap###config:overlap", ref audioOverlap)) {
+ Configuration.AudioOverlap = audioOverlap;
+ Configuration.Save();
+ };
+ ImGuiComponents.HelpMarker("Overlap audio instead of stopping an already playing audio");
+ ImGui.Spacing();
ImGui.TextColoredWrapped(headingColor, "Play sound on");
// when self falls options
From c6782fe13b443894be16d93d491a5765b3efbda7 Mon Sep 17 00:00:00 2001
From: DaBultz <27208227+DaBultz@users.noreply.github.com>
Date: Mon, 4 May 2026 14:20:02 +0200
Subject: [PATCH 6/8] stopping audio playback now stops all created players
now we keep track of all active players, instead of only 1. all
operations modifying the activePlayers is done via a Lock, to ensure
multiple tasks doesn't modify the list at the same time
---
OofPlugin/SoundManager.cs | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/OofPlugin/SoundManager.cs b/OofPlugin/SoundManager.cs
index 53076ba..432c7d0 100644
--- a/OofPlugin/SoundManager.cs
+++ b/OofPlugin/SoundManager.cs
@@ -3,6 +3,7 @@
using SoundFlow.Components;
using SoundFlow.Providers;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
@@ -23,7 +24,7 @@ internal class SoundManager : IDisposable {
private MiniAudioEngine engine;
private AudioPlaybackDevice playbackDevice;
private AudioFormat audioFormat;
- private SoundPlayer? currentPlayer;
+ private List activePlayers = new();
internal CancellationTokenSource CancelToken;
@@ -43,6 +44,9 @@ public SoundManager(OofPlugin plugin) {
LoadFile();
+ playbackDevice.Start();
+
+
CancelToken = new CancellationTokenSource();
Task.Run(() => OofAudioPolling(CancelToken.Token));
}
@@ -56,31 +60,37 @@ public void LoadFile() {
soundFile = Configuration.DefaultSoundImportPath;
}
+ ///
+ /// Stops all active sound players and cleans up their resources.
+ ///
public void Stop() {
- // When an audio plays this will cause a tiny lag spike, but as this is only used in the ConfigWindow, it
- // should be fine
- playbackDevice.Stop();
+ lock (activePlayers) {
+ activePlayers.ForEach(x => {
+ x.Stop();
+ playbackDevice.MasterMixer.RemoveComponent(x);
+ x.Dispose();
+ });
+
+ activePlayers.Clear();
+ }
}
public void Play(CancellationToken token, float volume = 1f) {
_ = Task.Run(() => {
- if (!Configuration.AudioOverlap && currentPlayer != null) {
- currentPlayer.Stop();
- playbackDevice.MasterMixer.RemoveComponent(currentPlayer);
- currentPlayer.Dispose();
- currentPlayer = null;
+ if (!Configuration.AudioOverlap) {
+ Stop();
}
var dataProvider = new StreamDataProvider(engine, audioFormat, File.OpenRead(soundFile));
var player = new SoundPlayer(engine, audioFormat, dataProvider);
- currentPlayer = player;
+ lock(activePlayers) { activePlayers.Add(player); }
playbackDevice.MasterMixer.AddComponent(player);
- playbackDevice.Start();
// this cleans up after the playback ends
player.PlaybackEnded += (_, _) => {
+ lock(activePlayers) { activePlayers.Remove(player); };
player.Stop();
playbackDevice.MasterMixer.RemoveComponent(player);
player.Dispose();
From e605a1f19cba3ff50a2c47ab56abff638a8252f9 Mon Sep 17 00:00:00 2001
From: DaBultz <27208227+DaBultz@users.noreply.github.com>
Date: Mon, 4 May 2026 14:51:28 +0200
Subject: [PATCH 7/8] audio device can now be reinitialized
Reintiialize/Initializing the audio device, will first safely dispose
the old one, before attempting to make a new one.
---
OofPlugin/SoundManager.cs | 72 +++++++++++++++++++++----------
OofPlugin/Windows/ConfigWindow.cs | 9 +++-
2 files changed, 57 insertions(+), 24 deletions(-)
diff --git a/OofPlugin/SoundManager.cs b/OofPlugin/SoundManager.cs
index 432c7d0..bc3268b 100644
--- a/OofPlugin/SoundManager.cs
+++ b/OofPlugin/SoundManager.cs
@@ -10,7 +10,6 @@
using System.Threading;
using System.Threading.Tasks;
using AudioFormat = SoundFlow.Structs.AudioFormat;
-using PlaybackState = SoundFlow.Enums.PlaybackState;
namespace OofPlugin;
@@ -19,12 +18,12 @@ internal class SoundManager : IDisposable {
private readonly DeadPlayersList DeadPlayersList;
// sound
- private string soundFile;
+ private string? soundFile;
- private MiniAudioEngine engine;
- private AudioPlaybackDevice playbackDevice;
+ private readonly MiniAudioEngine engine;
+ private AudioPlaybackDevice? playbackDevice;
private AudioFormat audioFormat;
- private List activePlayers = new();
+ private readonly List activePlayers = new();
internal CancellationTokenSource CancelToken;
@@ -33,19 +32,10 @@ public SoundManager(OofPlugin plugin) {
Configuration = plugin.Configuration;
DeadPlayersList = plugin.DeadPlayersList;
- engine = new MiniAudioEngine();
- engine.UpdateAudioDevicesInfo();
-
- // Get the system default playback device
- var defaultDevice = engine.PlaybackDevices.FirstOrDefault(x => x.IsDefault);
-
- audioFormat = AudioFormat.DvdHq;
- playbackDevice = engine.InitializePlaybackDevice(defaultDevice, audioFormat);
-
LoadFile();
- playbackDevice.Start();
-
+ engine = new MiniAudioEngine();
+ InitializeAudioDevice();
CancelToken = new CancellationTokenSource();
Task.Run(() => OofAudioPolling(CancelToken.Token));
@@ -60,6 +50,40 @@ public void LoadFile() {
soundFile = Configuration.DefaultSoundImportPath;
}
+ ///
+ /// Create the playback device using the current system default output and starts it, if it's already
+ /// created it will be destroyed and re-created, safely
+ /// Falls back to logging an error if device discovery or initialization fails.
+ ///
+ public void InitializeAudioDevice() {
+ DestroyAudioDevice();
+
+ try {
+ engine.UpdateAudioDevicesInfo();
+
+ // Get the system default playback device
+ var defaultDevice = engine.PlaybackDevices.FirstOrDefault(x => x.IsDefault);
+
+ audioFormat = AudioFormat.DvdHq;
+ playbackDevice = engine.InitializePlaybackDevice(defaultDevice, audioFormat);
+ playbackDevice.Start();
+ }
+ catch (Exception ex) {
+ Dalamud.Log.Error(ex, "OOF: OofAudioRecreateAudioDevice failed");
+ }
+ }
+
+
+ private void DestroyAudioDevice() {
+ Stop();
+
+ if(playbackDevice != null) {
+ playbackDevice.Stop();
+ playbackDevice.Dispose();
+ }
+
+ }
+
///
/// Stops all active sound players and cleans up their resources.
///
@@ -67,7 +91,7 @@ public void Stop() {
lock (activePlayers) {
activePlayers.ForEach(x => {
x.Stop();
- playbackDevice.MasterMixer.RemoveComponent(x);
+ playbackDevice?.MasterMixer.RemoveComponent(x);
x.Dispose();
});
@@ -80,23 +104,27 @@ public void Play(CancellationToken token, float volume = 1f) {
if (!Configuration.AudioOverlap) {
Stop();
}
+
+ if (soundFile == null) {
+ Dalamud.Log.Error("OOF: No sound file found to play.");
+ return;
+ }
var dataProvider = new StreamDataProvider(engine, audioFormat, File.OpenRead(soundFile));
var player = new SoundPlayer(engine, audioFormat, dataProvider);
lock(activePlayers) { activePlayers.Add(player); }
- playbackDevice.MasterMixer.AddComponent(player);
+ playbackDevice?.MasterMixer.AddComponent(player);
// this cleans up after the playback ends
player.PlaybackEnded += (_, _) => {
- lock(activePlayers) { activePlayers.Remove(player); };
+ lock(activePlayers) { activePlayers.Remove(player); }
player.Stop();
- playbackDevice.MasterMixer.RemoveComponent(player);
+ playbackDevice?.MasterMixer.RemoveComponent(player);
player.Dispose();
};
-
player.Volume = volume;
player.Play();
}, token);
@@ -174,7 +202,7 @@ public void Dispose() {
CancelToken.Cancel();
CancelToken.Dispose();
- playbackDevice.Dispose();
+ playbackDevice?.Dispose();
engine.Dispose();
}
}
\ No newline at end of file
diff --git a/OofPlugin/Windows/ConfigWindow.cs b/OofPlugin/Windows/ConfigWindow.cs
index 19899a2..48f2bc3 100644
--- a/OofPlugin/Windows/ConfigWindow.cs
+++ b/OofPlugin/Windows/ConfigWindow.cs
@@ -66,6 +66,13 @@ public override void Draw() {
Configuration.Save();
};
ImGuiComponents.HelpMarker("Overlap audio instead of stopping an already playing audio");
+
+ ImGui.Spacing();
+ if (ImGui.Button("Reload Audio Devices")) {
+ Plugin.SoundManager.InitializeAudioDevice();
+ }
+
+
ImGui.Spacing();
ImGui.TextColoredWrapped(headingColor, "Play sound on");
@@ -329,7 +336,5 @@ public static FileDialogManager SetupFileManager() {
return fileManager;
}
-
-
}
From fd4d0a815ae38bf578b759d642ab217ee56ec915 Mon Sep 17 00:00:00 2001
From: DaBultz <27208227+DaBultz@users.noreply.github.com>
Date: Sun, 10 May 2026 21:05:59 +0200
Subject: [PATCH 8/8] only play/stop audio when player is initialized
Incase the initilized fails, this should prevent crashes from
happending, when it attempts to play an audio/stop one.
There might be a better way of handling this
---
OofPlugin/SoundManager.cs | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/OofPlugin/SoundManager.cs b/OofPlugin/SoundManager.cs
index bc3268b..8a1633c 100644
--- a/OofPlugin/SoundManager.cs
+++ b/OofPlugin/SoundManager.cs
@@ -25,7 +25,8 @@ internal class SoundManager : IDisposable {
private AudioFormat audioFormat;
private readonly List activePlayers = new();
-
+ private bool isInitialized = false;
+
internal CancellationTokenSource CancelToken;
public SoundManager(OofPlugin plugin) {
@@ -67,8 +68,11 @@ public void InitializeAudioDevice() {
audioFormat = AudioFormat.DvdHq;
playbackDevice = engine.InitializePlaybackDevice(defaultDevice, audioFormat);
playbackDevice.Start();
+
+ isInitialized = true;
}
catch (Exception ex) {
+ isInitialized = false;
Dalamud.Log.Error(ex, "OOF: OofAudioRecreateAudioDevice failed");
}
}
@@ -88,6 +92,8 @@ private void DestroyAudioDevice() {
/// Stops all active sound players and cleans up their resources.
///
public void Stop() {
+ if (!isInitialized) return;
+
lock (activePlayers) {
activePlayers.ForEach(x => {
x.Stop();
@@ -100,6 +106,8 @@ public void Stop() {
}
public void Play(CancellationToken token, float volume = 1f) {
+ if (!isInitialized) return;
+
_ = Task.Run(() => {
if (!Configuration.AudioOverlap) {
Stop();