Skip to content

Commit fb3cad1

Browse files
committed
update,more task , more fix
1 parent cdca962 commit fb3cad1

8 files changed

Lines changed: 233 additions & 103 deletions

File tree

EXILED/Exiled.API/Features/Audio/PcmSources/MixerSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public sealed class MixerSource : IPcmSource
3232
/// Initializes a new instance of the <see cref="MixerSource"/> class with the specified initial sources.
3333
/// </summary>
3434
/// <param name="initialSources">An array of <see cref="IPcmSource"/> instances to mix.</param>
35-
public MixerSource(params IPcmSource[] initialSources)
35+
public MixerSource(IEnumerable<IPcmSource> initialSources)
3636
{
3737
if (initialSources != null)
3838
sources.AddRange(initialSources.Where(s => s != null));

EXILED/Exiled.API/Features/Audio/PcmSources/PlayerVoiceSource.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
namespace Exiled.API.Features.Audio.PcmSources
99
{
1010
using System.Buffers;
11-
using System.Collections.Concurrent;
1211
using System.Collections.Generic;
1312

1413
using Exiled.API.Features;

EXILED/Exiled.API/Features/Audio/PcmSources/PreloadedPcmSource.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Exiled.API.Features.Audio.PcmSources
1919
/// <summary>
2020
/// Provides a <see cref="IPcmSource"/> preloaded with Pcm data or file.
2121
/// </summary>
22-
public sealed class PreloadedPcmSource : IPcmSource
22+
public sealed class PreloadedPcmSource : IPcmSource, IAsyncPcmSource
2323
{
2424
private float[] data;
2525
private int pos;
@@ -46,7 +46,7 @@ public PreloadedPcmSource(string path)
4646
}
4747
catch (Exception ex)
4848
{
49-
Log.Error($"[PreloadedPcmSource] Background load failed for '{path}'.\n{ex}");
49+
Log.Error($"[PreloadedPcmSource] Failed to load audio from path: {path} | Error: {ex.Message}");
5050
isFailed = true;
5151
}
5252
});
@@ -87,6 +87,11 @@ public double CurrentTime
8787
set => Seek(value);
8888
}
8989

90+
/// <summary>
91+
/// Gets a value indicating whether the source failed to load.
92+
/// </summary>
93+
public bool IsFailed => isFailed;
94+
9095
/// <summary>
9196
/// Reads a sequence of PCM samples from the preloaded buffer into the specified array.
9297
/// </summary>

EXILED/Exiled.API/Features/Audio/PcmSources/VoiceRssTtsSource.cs

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace Exiled.API.Features.Audio.PcmSources
1010
using System;
1111
using System.Collections.Generic;
1212
using System.Linq;
13+
using System.Threading.Tasks;
1314

1415
using Exiled.API.Features;
1516
using Exiled.API.Interfaces.Audio;
@@ -22,17 +23,19 @@ namespace Exiled.API.Features.Audio.PcmSources
2223
/// <summary>
2324
/// Provides a <see cref="IPcmSource"/> that converts text to speech using the <see href="https://www.voicerss.org/">VoiceRSS</see> Text-to-Speech API.
2425
/// </summary>
25-
public sealed class VoiceRssTtsSource : IPcmSource
26+
public sealed class VoiceRssTtsSource : IPcmSource, IAsyncPcmSource
2627
{
2728
private const string ApiEndpoint = "https://api.voicerss.org/";
2829
private const string AudioFormat = "48khz_16bit_mono";
2930

31+
private static readonly Dictionary<string, DateTime> BlacklistKeys = new();
32+
3033
private IPcmSource internalSource;
3134
private UnityWebRequest webRequest;
3235
private CoroutineHandle downloadRoutine;
3336

34-
private bool isReady = false;
35-
private bool isFailed = false;
37+
private volatile bool isReady = false;
38+
private volatile bool isFailed = false;
3639

3740
/// <summary>
3841
/// Initializes a new instance of the <see cref="VoiceRssTtsSource"/> class.
@@ -99,6 +102,11 @@ public double CurrentTime
99102
/// </summary>
100103
public bool Ended => isFailed || (isReady && internalSource != null && internalSource.Ended);
101104

105+
/// <summary>
106+
/// Gets a value indicating whether the source failed to load.
107+
/// </summary>
108+
public bool IsFailed => isFailed;
109+
102110
/// <summary>
103111
/// Reads PCM data from the audio source into the specified buffer.
104112
/// </summary>
@@ -154,8 +162,6 @@ public void Dispose()
154162

155163
private IEnumerator<float> DownloadRoutine(string text, IEnumerable<string> apiKeys, string language, string voice, int rate)
156164
{
157-
webRequest = null;
158-
159165
string clampedRate = Math.Clamp(rate, -10, 10).ToString();
160166
string textEscaped = Uri.EscapeDataString(text);
161167
string langEscaped = Uri.EscapeDataString(language);
@@ -168,10 +174,26 @@ private IEnumerator<float> DownloadRoutine(string text, IEnumerable<string> apiK
168174
if (string.IsNullOrWhiteSpace(apiKey))
169175
continue;
170176

177+
if (BlacklistKeys.TryGetValue(apiKey, out DateTime exhaustedAt))
178+
{
179+
if (DateTime.UtcNow.Day == exhaustedAt.Day)
180+
continue;
181+
182+
BlacklistKeys.Remove(apiKey);
183+
}
184+
171185
string url = $"{ApiEndpoint}?key={Uri.EscapeDataString(apiKey)}&hl={langEscaped}&c=WAV&f={AudioFormat}&r={clampedRate}&src={textEscaped}{voiceEscaped}";
172186

173187
webRequest?.Dispose();
174-
webRequest = UnityWebRequest.Get(url);
188+
try
189+
{
190+
webRequest = UnityWebRequest.Get(url);
191+
}
192+
catch (Exception ex)
193+
{
194+
Log.Error($"[VoiceRssTtsSource] Failed to get Url '{url}. Error: {ex.Message}");
195+
break;
196+
}
175197

176198
yield return Timing.WaitUntilDone(webRequest.SendWebRequest());
177199

@@ -189,6 +211,7 @@ private IEnumerator<float> DownloadRoutine(string text, IEnumerable<string> apiK
189211
if (errorMessage.Contains("limit") || errorMessage.Contains("expired") || errorMessage.Contains("inactive") || errorMessage.Contains("API key"))
190212
{
191213
Log.Warn($"[VoiceRssTtsSource] Key issue, key: '{apiKey}', Error : {errorMessage}. Switching to another key...");
214+
BlacklistKeys[apiKey] = DateTime.UtcNow;
192215
continue;
193216
}
194217
else
@@ -205,29 +228,40 @@ private IEnumerator<float> DownloadRoutine(string text, IEnumerable<string> apiK
205228
if (!successfulDownload)
206229
{
207230
isFailed = true;
231+
webRequest?.Dispose();
232+
webRequest = null;
208233
yield break;
209234
}
210235

211-
try
236+
byte[] rawBytes = webRequest.downloadHandler.data;
237+
webRequest.Dispose();
238+
webRequest = null;
239+
240+
Task<AudioData> toPcmTask = Task.Run(() => WavUtility.WavToPcm(rawBytes));
241+
242+
yield return Timing.WaitUntilTrue(() => toPcmTask.IsCompleted);
243+
244+
if (toPcmTask.IsFaulted)
212245
{
213-
byte[] rawBytes = webRequest.downloadHandler.data;
214-
AudioData audioData = WavUtility.WavToPcm(rawBytes);
215-
audioData.TrackInfo.Path = $"VoiceRSS: {text}";
246+
Log.Error($"[VoiceRssTtsSource] Error read the downloaded file! \nError: {toPcmTask.Exception?.InnerException?.Message ?? toPcmTask.Exception?.Message}");
247+
isFailed = true;
248+
yield break;
249+
}
250+
251+
AudioData audioData = toPcmTask.Result;
252+
audioData.TrackInfo.Path = $"VoiceRSS: {text}";
216253

254+
try
255+
{
217256
internalSource = new PreloadedPcmSource(audioData.Pcm);
218257
TrackInfo = audioData.TrackInfo;
219258
isReady = true;
220259
}
221-
catch (Exception e)
260+
catch (Exception ex)
222261
{
223-
Log.Error($"[VoiceRssTtsSource] Parsing Error! \nDetails: {e.Message}");
262+
Log.Error($"[VoiceRssTtsSource] Failed to create internal source! \nError: {ex.InnerException?.Message ?? ex.Message}");
224263
isFailed = true;
225264
}
226-
finally
227-
{
228-
webRequest?.Dispose();
229-
webRequest = null;
230-
}
231265
}
232266
}
233267
}

EXILED/Exiled.API/Features/Audio/PcmSources/PreloadWebWavPcmSource.cs renamed to EXILED/Exiled.API/Features/Audio/PcmSources/WebWavPcmSource.cs

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// -----------------------------------------------------------------------
2-
// <copyright file="PreloadWebWavPcmSource.cs" company="ExMod Team">
2+
// <copyright file="WebWavPcmSource.cs" company="ExMod Team">
33
// Copyright (c) ExMod Team. All rights reserved.
44
// Licensed under the CC BY-SA 3.0 license.
55
// </copyright>
@@ -9,6 +9,7 @@ namespace Exiled.API.Features.Audio.PcmSources
99
{
1010
using System;
1111
using System.Collections.Generic;
12+
using System.Threading.Tasks;
1213

1314
using Exiled.API.Features;
1415
using Exiled.API.Interfaces.Audio;
@@ -21,20 +22,20 @@ namespace Exiled.API.Features.Audio.PcmSources
2122
/// <summary>
2223
/// Provides a <see cref="IPcmSource"/> that downloads a .wav file from a URL and preloads it for playback.
2324
/// </summary>
24-
public sealed class PreloadWebWavPcmSource : IPcmSource
25+
public sealed class WebWavPcmSource : IPcmSource, IAsyncPcmSource
2526
{
2627
private IPcmSource internalSource;
2728
private UnityWebRequest webRequest;
2829
private CoroutineHandle downloadRoutine;
2930

30-
private bool isReady = false;
31-
private bool isFailed = false;
31+
private volatile bool isReady = false;
32+
private volatile bool isFailed = false;
3233

3334
/// <summary>
34-
/// Initializes a new instance of the <see cref="PreloadWebWavPcmSource"/> class.
35+
/// Initializes a new instance of the <see cref="WebWavPcmSource"/> class.
3536
/// </summary>
3637
/// <param name="url">The direct URL to the .wav file.</param>
37-
public PreloadWebWavPcmSource(string url)
38+
public WebWavPcmSource(string url)
3839
{
3940
TrackInfo = default;
4041
downloadRoutine = Timing.RunCoroutine(Download(url));
@@ -64,6 +65,11 @@ public double CurrentTime
6465
/// </summary>
6566
public bool Ended => isFailed || (isReady && internalSource != null && internalSource.Ended);
6667

68+
/// <summary>
69+
/// Gets a value indicating whether the source failed to load.
70+
/// </summary>
71+
public bool IsFailed => isFailed;
72+
6773
/// <summary>
6874
/// Reads PCM data from the audio source into the specified buffer.
6975
/// </summary>
@@ -105,7 +111,7 @@ public void Reset()
105111
}
106112

107113
/// <summary>
108-
/// Releases all resources used by the <see cref="PreloadWebWavPcmSource"/>.
114+
/// Releases all resources used by the <see cref="WebWavPcmSource"/>.
109115
/// </summary>
110116
public void Dispose()
111117
{
@@ -119,48 +125,60 @@ public void Dispose()
119125

120126
private IEnumerator<float> Download(string url)
121127
{
122-
webRequest = null;
123-
124128
try
125129
{
126130
webRequest = UnityWebRequest.Get(url);
131+
webRequest.useHttpContinue = true;
127132
}
128133
catch (Exception ex)
129134
{
130-
Log.Error($"[WebPreloadWavPcmSource] Failed to download audio! URL: {url} | Error: {ex.Message}");
135+
Log.Error($"[WebWavPcmSource] Failed to download audio! URL: {url} | Error: {ex.Message}");
131136
isFailed = true;
137+
webRequest?.Dispose();
138+
webRequest = null;
132139
yield break;
133140
}
134141

135142
yield return Timing.WaitUntilDone(webRequest.SendWebRequest());
136143

137-
try
144+
if (webRequest.result != UnityWebRequest.Result.Success)
138145
{
139-
if (webRequest.result != UnityWebRequest.Result.Success)
140-
{
141-
Log.Error($"[WebPreloadWavPcmSource] Failed to download audio! URL: {url} | Error: {webRequest.error}");
142-
isFailed = true;
143-
yield break;
144-
}
146+
Log.Error($"[WebWavPcmSource] Failed to download audio! URL: {url} | Error: {webRequest.error}");
147+
isFailed = true;
148+
webRequest?.Dispose();
149+
webRequest = null;
150+
yield break;
151+
}
152+
153+
byte[] rawBytes = webRequest.downloadHandler.data;
154+
webRequest.Dispose();
155+
webRequest = null;
145156

146-
byte[] rawBytes = webRequest.downloadHandler.data;
147-
AudioData audioData = WavUtility.WavToPcm(rawBytes);
148-
audioData.TrackInfo.Path = url;
157+
Task<AudioData> toPcmTask = Task.Run(() => WavUtility.WavToPcm(rawBytes));
149158

159+
yield return Timing.WaitUntilTrue(() => toPcmTask.IsCompleted);
160+
161+
if (toPcmTask.IsFaulted)
162+
{
163+
Log.Error($"[WebPreloadWavPcmSource] Failed to read the downloaded file! Ensure the link points to a valid .WAV file. Error: {toPcmTask.Exception?.InnerException?.Message ?? toPcmTask.Exception?.Message}");
164+
isFailed = true;
165+
yield break;
166+
}
167+
168+
AudioData audioData = toPcmTask.Result;
169+
audioData.TrackInfo.Path = url;
170+
171+
try
172+
{
150173
internalSource = new PreloadedPcmSource(audioData.Pcm);
151174
TrackInfo = audioData.TrackInfo;
152175
isReady = true;
153176
}
154-
catch (Exception e)
177+
catch (Exception ex)
155178
{
156-
Log.Error($"[WebPreloadWavPcmSource] Failed to read the downloaded file! Ensure the link points to a valid .WAV file.\nException Details: {e.Message}");
179+
Log.Error($"[WebPreloadWavPcmSource] Failed to read the downloaded file! Ensure the link points to a valid .WAV file. Error: {ex.InnerException?.Message ?? ex.Message}");
157180
isFailed = true;
158181
}
159-
finally
160-
{
161-
webRequest?.Dispose();
162-
webRequest = null;
163-
}
164182
}
165183
}
166184
}

EXILED/Exiled.API/Features/Audio/WavUtility.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static IPcmSource CreatePcmSource(string path, bool stream = false, bool
3939
return new CachedPcmSource(path, path);
4040

4141
if (path.StartsWith("http"))
42-
return new PreloadWebWavPcmSource(path);
42+
return new WebWavPcmSource(path);
4343

4444
if (stream)
4545
return new WavStreamSource(path);
@@ -131,6 +131,12 @@ public static TrackData SkipHeader(Stream stream)
131131
{
132132
TrackData trackData = new();
133133

134+
if (stream.Length < 12)
135+
{
136+
Log.Error("[WavUtility] WAV file is too short to contain a valid header.");
137+
throw new InvalidDataException("WAV file is too short to contain a valid header.");
138+
}
139+
134140
Span<byte> headerBuffer = stackalloc byte[12];
135141
stream.Read(headerBuffer);
136142

@@ -141,6 +147,12 @@ public static TrackData SkipHeader(Stream stream)
141147
Span<byte> chunkHeader = stackalloc byte[8];
142148
while (true)
143149
{
150+
if (stream.Position + 8 > stream.Length)
151+
{
152+
Log.Error("[WavUtility] WAV file ended prematurely while parsing chunks.");
153+
throw new InvalidDataException("WAV file ended prematurely while parsing chunks.");
154+
}
155+
144156
int read = stream.Read(chunkHeader);
145157
if (read < 8)
146158
break;

0 commit comments

Comments
 (0)