Skip to content

Commit 5ad144e

Browse files
MS-crewlouis1706Someone-193
authored
feat: Speaker Api: Part Two (#762)
* Automatic id / throwing errors changed to log error * f * . * s * gf * eğH * added new özellik * update * update log * color on wrong order * wwait i will fix * finished * fix * pool clear * fAHH! * Fahh * better and more functionally * Return to pool doc * Cleanup & Breaking Changes for exiled 10 Fck this old useless things * Update Speaker.cs * Update Speaker.cs * Update Speaker.cs * little refactor * Performance improvement & fix Architectural problems in pooling * wth * . * fix return pool * . * d * removed Hard Coded values * Update Speaker.cs * fAHHHHH * fix * f * izabel * Release controller ID on pool return to prevent ID exhaustion * remove network chechks * for another pr * locals * fix: prevent controller ID conflicts by removing pooled speakers from AllInstances * s * base check for return pool * ffff * NOT MY CHANGES * Added Fade Volume Method, Time left property & setter for PlaybackProgress * fade in for play * TrackData & fade in for Play from pool * encoder clean for current time setter * Queue Track * added Static Events * Audio Time Events * fix Action errors break corrutine * Action id * more more C#18 YOLOOOOOOO * reorder * lazy instance for lists * shuffle * give credit to Fisher-Yates * i forgot to delete this * natural Fade * add linear fade option * Fix after fade volume stuck 0 * fixes * squash fix * fade corrutine fix maybe, im drowing help me is it OverApi idk? * Update Speaker.cs * Update Speaker.cs * simplify api & null safety & fix fade & add stop fade function * Remove Fade out because its doing api dirty and complicated * Clean Api & new Event * remove thing which im added * remeove old arg * doc * f * renameing * Standalone System * remove useles using which is i added * bool return * Add Filter * Open Modular Api + Url play + Player play * \ * add filter samples * nh * . * fix: 14.2.6 update (#781) * update * remove MarshmallowFF fix * Update doc 14.2.0.6 * fix footprint ctor for after player leaves tbh the error itself might come from whenever mirror decides to remove peers and stuff cuz async * I committed the commented out version when I was trying to recreate the bug 💔 * add docs + TryRaycastRoom fix --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> * Bump to v9.13.3 * t * directorys magic struct to struct * cache logic# * fix file name * pcm method * helper for url * refactor cache & add Playbacksettings class for too long param * It seems to get more complicated each time. * d * change ai filter to more optimized ai filter * simplfy playervoicesource * Update Speaker.cs * old way tasks maybe later * filter reset + change useless Concurrent * class to struct * fix slient crash probablty, memory leak, neww sources * add multiple key support for bypass free usage limit * renames * update,more task , more fix * i forgot this * i forgıt it * fixed index out of * task error protection & audio delaying 1 frame for nothing * private pool + get * delete summary * shit * you cant see me * Last touchs of mr selim * Change channel * new prop for asyncpcmsoruce * j --------- Co-authored-by: Yamato <66829532+louis1706@users.noreply.github.com> Co-authored-by: @Someone <45270312+Someone-193@users.noreply.github.com>
1 parent 696013d commit 5ad144e

25 files changed

Lines changed: 3467 additions & 255 deletions

EXILED/Exiled.API/Exiled.API.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<Reference Include="UnityEngine.PhysicsModule" HintPath="$(EXILED_REFERENCES)\UnityEngine.PhysicsModule.dll" Private="false" />
4141
<Reference Include="UnityEngine.AudioModule" HintPath="$(EXILED_REFERENCES)\UnityEngine.AudioModule.dll" Private="false" />
4242
<Reference Include="UnityEngine.UI" HintPath="$(EXILED_REFERENCES)\UnityEngine.UI.dll" Private="false" />
43+
<Reference Include="UnityEngine.UnityWebRequestModule" HintPath="$(EXILED_REFERENCES)\UnityEngine.UnityWebRequestModule.dll" Private="false" />
4344
<Reference Include="YamlDotNet" HintPath="$(EXILED_REFERENCES)\YamlDotNet.dll" Private="false" />
4445
<Reference Include="mscorlib" HintPath="$(EXILED_REFERENCES)\mscorlib.dll" Private="false" />
4546
<Reference Include="System" HintPath="$(EXILED_REFERENCES)\System.dll" Private="false" />
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="AudioDataStorage.cs" company="ExMod Team">
3+
// Copyright (c) ExMod Team. All rights reserved.
4+
// Licensed under the CC BY-SA 3.0 license.
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
namespace Exiled.API.Features.Audio
9+
{
10+
using System;
11+
using System.Collections.Concurrent;
12+
using System.Collections.Generic;
13+
using System.IO;
14+
15+
using Exiled.API.Structs.Audio;
16+
17+
using MEC;
18+
19+
using RoundRestarting;
20+
21+
using UnityEngine.Networking;
22+
23+
/// <summary>
24+
/// Manages a global in-memory storage of decoded PCM audio data. Once stored, audio can be played using <see cref="PcmSources.CachedPcmSource"/>.
25+
/// </summary>
26+
public static class AudioDataStorage
27+
{
28+
static AudioDataStorage()
29+
{
30+
AudioStorage = new();
31+
RoundRestart.OnRestartTriggered += OnRoundRestart;
32+
}
33+
34+
/// <summary>
35+
/// Gets the underlying storage, keyed by name.
36+
/// </summary>
37+
public static Dictionary<string, AudioData> AudioStorage { get; }
38+
39+
/// <summary>
40+
/// Gets or sets a value indicating whether the storage is automatically cleared when a round restart is triggered.
41+
/// </summary>
42+
public static bool ClearOnRoundRestart { get; set; } = true;
43+
44+
/// <summary>
45+
/// Loads and stores a local .wav file under the specified name.
46+
/// </summary>
47+
/// <param name="name">The unique storage key to assign to this audio.</param>
48+
/// <param name="path">The absolute path to the local .wav file.</param>
49+
/// <returns><c>true</c> if the file was successfully loaded and stored; otherwise, <c>false</c>.</returns>
50+
public static bool AddWav(string name, string path)
51+
{
52+
if (!ValidateName(name))
53+
return false;
54+
55+
if (AudioStorage.ContainsKey(name))
56+
{
57+
Log.Warn($"[AudioDataStorage] An entry with the key '{name}' already exists. Skipping add.");
58+
return false;
59+
}
60+
61+
if (path.StartsWith("http"))
62+
{
63+
Log.Error($"[AudioDataStorage] '{path}' is a URL. Use AudioDataStorage.AddUrl() for web sources.");
64+
return false;
65+
}
66+
67+
if (!File.Exists(path))
68+
{
69+
Log.Error($"[AudioDataStorage] Local file not found: '{path}'");
70+
return false;
71+
}
72+
73+
try
74+
{
75+
AudioData parsed = WavUtility.WavToPcm(path);
76+
return AudioStorage.TryAdd(name, parsed);
77+
}
78+
catch (Exception ex)
79+
{
80+
Log.Error($"[AudioDataStorage] Failed to load '{path}' into storage:\n{ex}");
81+
return false;
82+
}
83+
}
84+
85+
/// <summary>
86+
/// Stores raw PCM audio samples under the specified name.
87+
/// </summary>
88+
/// <param name="name">The unique storage key to assign.</param>
89+
/// <param name="pcm">The raw PCM float array to store.</param>
90+
/// <returns><c>true</c> if successfully added; otherwise, <c>false</c>.</returns>
91+
public static bool Add(string name, float[] pcm)
92+
{
93+
if (pcm == null)
94+
{
95+
Log.Error($"[AudioDataStorage] Cannot store null array for key '{name}'.");
96+
return false;
97+
}
98+
99+
TrackData trackInfo = new()
100+
{
101+
Title = name,
102+
Duration = (double)pcm.Length / VoiceChat.VoiceChatSettings.SampleRate,
103+
};
104+
105+
return Add(name, new AudioData(pcm, trackInfo));
106+
}
107+
108+
/// <summary>
109+
/// Stores a fully constructed <see cref="AudioData"/> under the specified name.
110+
/// </summary>
111+
/// <param name="name">The unique storage key to assign.</param>
112+
/// <param name="audioData">The <see cref="AudioData"/> to store.</param>
113+
/// <returns><c>true</c> if successfully added; otherwise, <c>false</c>.</returns>
114+
public static bool Add(string name, AudioData audioData)
115+
{
116+
if (!ValidateName(name))
117+
return false;
118+
119+
if (audioData.Pcm == null || audioData.Pcm.Length == 0)
120+
{
121+
Log.Error($"[AudioDataStorage] AudioData for key '{name}' has null or empty PCM.");
122+
return false;
123+
}
124+
125+
if (AudioStorage.ContainsKey(name))
126+
{
127+
Log.Warn($"[AudioDataStorage] An entry with the key '{name}' already exists. Skipping add.");
128+
return false;
129+
}
130+
131+
return AudioStorage.TryAdd(name, audioData);
132+
}
133+
134+
/// <summary>
135+
/// Starts an asynchronous download of a .wav file from the specified URL and adds it to the storage.
136+
/// </summary>
137+
/// <param name="name">The unique storage key to assign.</param>
138+
/// <param name="url">The HTTP or HTTPS URL pointing to a valid .wav file.</param>
139+
/// <returns>A <see cref="CoroutineHandle"/> for the running download coroutine.</returns>
140+
public static CoroutineHandle AddWavUrl(string name, string url) => Timing.RunCoroutine(AddUrlCoroutine(name, url));
141+
142+
/// <summary>
143+
/// Starts an asynchronous download of a .wav file from the specified URL and adds it to the storage.
144+
/// </summary>
145+
/// <param name="name">The unique storage key to assign.</param>
146+
/// <param name="url">The HTTP or HTTPS URL pointing to a valid .wav file.</param>
147+
/// <returns>A MEC-compatible <see cref="IEnumerator{T}"/> of <see cref="float"/>.</returns>
148+
public static IEnumerator<float> AddUrlCoroutine(string name, string url)
149+
{
150+
if (!ValidateName(name))
151+
yield break;
152+
153+
if (string.IsNullOrEmpty(url) || !url.StartsWith("http"))
154+
{
155+
Log.Error($"[AudioDataStorage] Invalid URL for key '{name}': '{url}'. Must start with http/https.");
156+
yield break;
157+
}
158+
159+
if (AudioStorage.ContainsKey(name))
160+
{
161+
Log.Warn($"[AudioDataStorage] An entry with the key '{name}' already exists. Skipping download.");
162+
yield break;
163+
}
164+
165+
using UnityWebRequest www = UnityWebRequest.Get(url);
166+
yield return Timing.WaitUntilDone(www.SendWebRequest());
167+
168+
if (www.result != UnityWebRequest.Result.Success)
169+
{
170+
Log.Error($"[AudioDataStorage] Download failed for '{url}': {www.error}");
171+
yield break;
172+
}
173+
174+
try
175+
{
176+
AudioData parsed = WavUtility.WavToPcm(www.downloadHandler.data);
177+
parsed.TrackInfo.Path = url;
178+
AudioStorage.TryAdd(name, parsed);
179+
}
180+
catch (Exception ex)
181+
{
182+
Log.Error($"[AudioDataStorage] Failed to parse downloaded WAV from '{url}':\n{ex}");
183+
}
184+
}
185+
186+
/// <summary>
187+
/// Removes a stored audio entry by name.
188+
/// </summary>
189+
/// <param name="name">The storage name/key to remove.</param>
190+
/// <returns><c>true</c> if the entry was found and removed; otherwise, <c>false</c>.</returns>
191+
public static bool Remove(string name) => AudioStorage.Remove(name, out _);
192+
193+
/// <summary>
194+
/// Clears all entries from the audio storage, freeing all associated memory.
195+
/// </summary>
196+
public static void Clear() => AudioStorage.Clear();
197+
198+
private static bool ValidateName(string name)
199+
{
200+
if (!string.IsNullOrEmpty(name))
201+
return true;
202+
203+
Log.Error("[AudioDataStorage] Storage name (key) cannot be null or empty.");
204+
return false;
205+
}
206+
207+
private static void OnRoundRestart()
208+
{
209+
if (ClearOnRoundRestart)
210+
Clear();
211+
}
212+
}
213+
}

0 commit comments

Comments
 (0)