Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions EXILED/Exiled.API/Features/Audio/WavUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.");
}
}
}
}
Expand Down
78 changes: 57 additions & 21 deletions EXILED/Exiled.API/Features/Toys/Speaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Exiled.API.Features.Toys
using Enums;

using Exiled.API.Features.Audio;
using Exiled.API.Features.Pools;

using Interfaces;

Expand All @@ -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;

Expand Down Expand Up @@ -288,20 +290,8 @@ public byte ControllerId
/// <param name="scale">The scale of the <see cref="Speaker"/>.</param>
/// <param name="spawn">Whether the <see cref="Speaker"/> should be initially spawned.</param>
/// <returns>The new <see cref="Speaker"/>.</returns>
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);

/// <summary>
/// Creates a new <see cref="Speaker"/>.
Expand All @@ -310,21 +300,61 @@ public static Speaker Create(Vector3? position, Vector3? rotation, Vector3? scal
/// <param name="spawn">Whether the <see cref="Speaker"/> should be initially spawned.</param>
/// <param name="worldPositionStays">Whether the <see cref="Speaker"/> should keep the same world position.</param>
/// <returns>The new <see cref="Speaker"/>.</returns>
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);

/// <summary>
/// Creates a new <see cref="Speaker"/>.
/// </summary>
/// <param name="parent">The parent transform to attach the <see cref="Speaker"/> to.</param>
/// <param name="position">The local position of the <see cref="Speaker"/>.</param>
/// <param name="scale">The scale of the <see cref="Speaker"/>.</param>
/// <param name="controllerId">The specific controller ID to assign. If null, the next available ID is used.</param>
/// <param name="spawn">Whether the <see cref="Speaker"/> should be initially spawned.</param>
/// <param name="worldPositionStays">Whether the <see cref="Speaker"/> should keep the same world position when parented.</param>
/// <returns>The new <see cref="Speaker"/>.</returns>
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;
}

/// <summary>
/// Gets the next available controller ID for a <see cref="Speaker"/>.
/// </summary>
/// <returns>The next available byte ID. If all IDs are currently in use, returns a default of 0.</returns>
public static byte GetNextFreeControllerId()
{
byte id = 0;
HashSet<byte> usedIds = HashSetPool<byte>.Pool.Get();

foreach (SpeakerToyPlaybackBase playbackBase in SpeakerToyPlaybackBase.AllInstances)
usedIds.Add(playbackBase.ControllerId);

if (usedIds.Count >= byte.MaxValue + 1)
{
HashSetPool<byte>.Pool.Return(usedIds);
return 0;
}

while (usedIds.Contains(id))
{
id++;
}

HashSetPool<byte>.Pool.Return(usedIds);
return id;
}

/// <summary>
/// Plays audio through this speaker.
/// </summary>
Expand Down Expand Up @@ -354,10 +384,16 @@ public static void Play(AudioMessage message, IEnumerable<Player> 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();
Expand Down
Loading