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();