diff --git a/EXILED/Exiled.API/Features/Audio/WavUtility.cs b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
index c1b1bc3f7..12ea4d32d 100644
--- a/EXILED/Exiled.API/Features/Audio/WavUtility.cs
+++ b/EXILED/Exiled.API/Features/Audio/WavUtility.cs
@@ -92,7 +92,10 @@ public static void SkipHeader(Stream stream)
short bits = BinaryPrimitives.ReadInt16LittleEndian(fmtData.Slice(14, 2));
if (format != 1 || channels != 1 || rate != VoiceChatSettings.SampleRate || bits != 16)
- throw new InvalidDataException($"Invalid WAV format (format={format}, channels={channels}, rate={rate}, bits={bits}). Expected PCM16, mono and {VoiceChatSettings.SampleRate}Hz.");
+ {
+ Log.Error($"[Speaker] Invalid WAV format (format={format}, channels={channels}, rate={rate}, bits={bits}). Expected PCM16, mono and {VoiceChatSettings.SampleRate}Hz.");
+ throw new InvalidDataException("Unsupported WAV format.");
+ }
if (chunkSize > 16)
stream.Seek(chunkSize - 16, SeekOrigin.Current);
@@ -109,7 +112,10 @@ public static void SkipHeader(Stream stream)
}
if (stream.Position >= stream.Length)
- throw new InvalidDataException("WAV file does not contain a 'data' chunk.");
+ {
+ Log.Error("[Speaker] WAV file does not contain a 'data' chunk.");
+ throw new InvalidDataException("Missing 'data' chunk in WAV file.");
+ }
}
}
}
diff --git a/EXILED/Exiled.API/Features/Toys/Speaker.cs b/EXILED/Exiled.API/Features/Toys/Speaker.cs
index 4478c1143..eb5106e72 100644
--- a/EXILED/Exiled.API/Features/Toys/Speaker.cs
+++ b/EXILED/Exiled.API/Features/Toys/Speaker.cs
@@ -16,6 +16,7 @@ namespace Exiled.API.Features.Toys
using Enums;
using Exiled.API.Features.Audio;
+ using Exiled.API.Features.Pools;
using Interfaces;
@@ -29,6 +30,7 @@ namespace Exiled.API.Features.Toys
using VoiceChat.Codec;
using VoiceChat.Codec.Enums;
using VoiceChat.Networking;
+ using VoiceChat.Playbacks;
using Object = UnityEngine.Object;
@@ -288,20 +290,8 @@ public byte ControllerId
/// The scale of the .
/// Whether the should be initially spawned.
/// The new .
- public static Speaker Create(Vector3? position, Vector3? rotation, Vector3? scale, bool spawn)
- {
- Speaker speaker = new(Object.Instantiate(Prefab))
- {
- Position = position ?? Vector3.zero,
- Rotation = Quaternion.Euler(rotation ?? Vector3.zero),
- Scale = scale ?? Vector3.one,
- };
-
- if (spawn)
- speaker.Spawn();
-
- return speaker;
- }
+ [Obsolete("Use the Create(parent, position, scale, controllerId, spawn, worldPositonStays) method, rotation is useless.")]
+ public static Speaker Create(Vector3? position, Vector3? rotation, Vector3? scale, bool spawn) => Create(parent: null, position: position, scale: scale, controllerId: null, spawn: spawn, worldPositionStays: true);
///
/// Creates a new .
@@ -310,21 +300,61 @@ public static Speaker Create(Vector3? position, Vector3? rotation, Vector3? scal
/// Whether the should be initially spawned.
/// Whether the should keep the same world position.
/// The new .
- public static Speaker Create(Transform transform, bool spawn, bool worldPositionStays = true)
+ public static Speaker Create(Transform transform, bool spawn, bool worldPositionStays = true) => Create(parent: transform, position: Vector3.zero, scale: transform.localScale.normalized, controllerId: null, spawn: spawn, worldPositionStays: worldPositionStays);
+
+ ///
+ /// Creates a new .
+ ///
+ /// The parent transform to attach the to.
+ /// The local position of the .
+ /// The scale of the .
+ /// The specific controller ID to assign. If null, the next available ID is used.
+ /// Whether the should be initially spawned.
+ /// Whether the should keep the same world position when parented.
+ /// The new .
+ public static Speaker Create(Transform parent = null, Vector3? position = null, Vector3? scale = null, byte? controllerId = null, bool spawn = true, bool worldPositionStays = true)
{
- Speaker speaker = new(Object.Instantiate(Prefab, transform, worldPositionStays))
+ Speaker speaker = new(Object.Instantiate(Prefab, parent, worldPositionStays))
{
- Position = transform.position,
- Rotation = transform.rotation,
- Scale = transform.localScale.normalized,
+ Scale = scale ?? Vector3.one,
+ ControllerId = controllerId ?? GetNextFreeControllerId(),
};
+ speaker.Transform.localPosition = position ?? Vector3.zero;
+
if (spawn)
speaker.Spawn();
return speaker;
}
+ ///
+ /// Gets the next available controller ID for a .
+ ///
+ /// The next available byte ID. If all IDs are currently in use, returns a default of 0.
+ public static byte GetNextFreeControllerId()
+ {
+ byte id = 0;
+ HashSet usedIds = HashSetPool.Pool.Get();
+
+ foreach (SpeakerToyPlaybackBase playbackBase in SpeakerToyPlaybackBase.AllInstances)
+ usedIds.Add(playbackBase.ControllerId);
+
+ if (usedIds.Count >= byte.MaxValue + 1)
+ {
+ HashSetPool.Pool.Return(usedIds);
+ return 0;
+ }
+
+ while (usedIds.Contains(id))
+ {
+ id++;
+ }
+
+ HashSetPool.Pool.Return(usedIds);
+ return id;
+ }
+
///
/// Plays audio through this speaker.
///
@@ -354,10 +384,16 @@ public static void Play(AudioMessage message, IEnumerable targets = null
public void Play(string path, bool stream = false, bool destroyAfter = false, bool loop = false)
{
if (!File.Exists(path))
- throw new FileNotFoundException("The specified file does not exist.", path);
+ {
+ Log.Error($"[Speaker] The specified file does not exist, path: `{path}`.");
+ return;
+ }
if (!path.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
- throw new NotSupportedException($"The file type '{Path.GetExtension(path)}' is not supported. Please use .wav file.");
+ {
+ Log.Error($"[Speaker] The file type '{Path.GetExtension(path)}' is not supported. Please use .wav file.");
+ return;
+ }
TryInitializePlayBack();
Stop();