Skip to content

Commit 9379a36

Browse files
committed
update,more task , more fix
1 parent 6ccf6f8 commit 9379a36

10 files changed

Lines changed: 308 additions & 127 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ static AudioDataStorage()
4747
/// <param name="name">The unique storage key to assign to this audio.</param>
4848
/// <param name="path">The absolute path to the local .wav file.</param>
4949
/// <returns><c>true</c> if the file was successfully loaded and stored; otherwise, <c>false</c>.</returns>
50-
public static bool Add(string name, string path)
50+
public static bool AddWav(string name, string path)
5151
{
5252
if (!ValidateName(name))
5353
return false;
@@ -137,7 +137,7 @@ public static bool Add(string name, AudioData audioData)
137137
/// <param name="name">The unique storage key to assign.</param>
138138
/// <param name="url">The HTTP or HTTPS URL pointing to a valid .wav file.</param>
139139
/// <returns>A <see cref="CoroutineHandle"/> for the running download coroutine.</returns>
140-
public static CoroutineHandle AddUrl(string name, string url) => Timing.RunCoroutine(AddUrlCoroutine(name, url));
140+
public static CoroutineHandle AddWavUrl(string name, string url) => Timing.RunCoroutine(AddUrlCoroutine(name, url));
141141

142142
/// <summary>
143143
/// Starts an asynchronous download of a .wav file from the specified URL and adds it to the storage.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public CachedPcmSource(string name, string path)
6161

6262
if (!AudioDataStorage.AudioStorage.ContainsKey(name))
6363
{
64-
if (!AudioDataStorage.Add(name, path))
64+
if (!AudioDataStorage.AddWav(name, path))
6565
{
6666
Log.Error($"[CachedPcmSource] Failed to load local file '{path}' into cache under the name '{name}'.");
6767
throw new FileNotFoundException($"Failed to cache and load '{path}'.");

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: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Exiled.API.Features.Audio.PcmSources
99
{
1010
using System;
11+
using System.Threading.Tasks;
1112

1213
using Exiled.API.Features.Audio;
1314
using Exiled.API.Interfaces.Audio;
@@ -18,20 +19,37 @@ namespace Exiled.API.Features.Audio.PcmSources
1819
/// <summary>
1920
/// Provides a <see cref="IPcmSource"/> preloaded with Pcm data or file.
2021
/// </summary>
21-
public sealed class PreloadedPcmSource : IPcmSource
22+
public sealed class PreloadedPcmSource : IPcmSource, IAsyncPcmSource
2223
{
23-
private readonly float[] data;
24+
private float[] data;
2425
private int pos;
2526

27+
private volatile bool isReady = false;
28+
private volatile bool isFailed = false;
29+
2630
/// <summary>
2731
/// Initializes a new instance of the <see cref="PreloadedPcmSource"/> class.
2832
/// </summary>
2933
/// <param name="path">The path to the audio file.</param>
3034
public PreloadedPcmSource(string path)
3135
{
32-
AudioData result = WavUtility.WavToPcm(path);
33-
data = result.Pcm;
34-
TrackInfo = result.TrackInfo;
36+
TrackInfo = new TrackData { Path = path, Duration = 0.0 };
37+
38+
Task.Run(() =>
39+
{
40+
try
41+
{
42+
AudioData result = WavUtility.WavToPcm(path);
43+
data = result.Pcm;
44+
TrackInfo = result.TrackInfo;
45+
isReady = true;
46+
}
47+
catch (Exception ex)
48+
{
49+
Log.Error($"[PreloadedPcmSource] Failed to load audio from path: {path} | Error: {ex.Message}");
50+
isFailed = true;
51+
}
52+
});
3553
}
3654

3755
/// <summary>
@@ -41,33 +59,39 @@ public PreloadedPcmSource(string path)
4159
public PreloadedPcmSource(float[] pcmData)
4260
{
4361
data = pcmData;
62+
isReady = true;
4463
TrackInfo = new TrackData { Duration = TotalDuration };
4564
}
4665

4766
/// <summary>
4867
/// Gets the metadata of the loaded track.
4968
/// </summary>
50-
public TrackData TrackInfo { get; }
69+
public TrackData TrackInfo { get; private set; }
5170

5271
/// <summary>
5372
/// Gets a value indicating whether the end of the PCM data buffer has been reached.
5473
/// </summary>
55-
public bool Ended => pos >= data.Length;
74+
public bool Ended => isFailed || (isReady && pos >= data.Length);
5675

5776
/// <summary>
5877
/// Gets the total duration of the audio in seconds.
5978
/// </summary>
60-
public double TotalDuration => (double)data.Length / VoiceChatSettings.SampleRate;
79+
public double TotalDuration => isReady && data != null ? (double)data.Length / VoiceChatSettings.SampleRate : 0.0;
6180

6281
/// <summary>
6382
/// Gets or sets the current playback position in seconds.
6483
/// </summary>
6584
public double CurrentTime
6685
{
67-
get => (double)pos / VoiceChatSettings.SampleRate;
86+
get => isReady ? (double)pos / VoiceChatSettings.SampleRate : 0.0;
6887
set => Seek(value);
6988
}
7089

90+
/// <summary>
91+
/// Gets a value indicating whether the source failed to load.
92+
/// </summary>
93+
public bool IsFailed => isFailed;
94+
7195
/// <summary>
7296
/// Reads a sequence of PCM samples from the preloaded buffer into the specified array.
7397
/// </summary>
@@ -77,6 +101,15 @@ public double CurrentTime
77101
/// <returns>The number of samples read into <paramref name="buffer"/>.</returns>
78102
public int Read(float[] buffer, int offset, int count)
79103
{
104+
if (isFailed)
105+
return 0;
106+
107+
if (!isReady || data == null)
108+
{
109+
Array.Clear(buffer, offset, count);
110+
return count;
111+
}
112+
80113
int read = Math.Min(count, data.Length - pos);
81114
Array.Copy(data, pos, buffer, offset, read);
82115
pos += read;
@@ -90,6 +123,9 @@ public int Read(float[] buffer, int offset, int count)
90123
/// <param name="seconds">The target position in seconds.</param>
91124
public void Seek(double seconds)
92125
{
126+
if (!isReady || data == null)
127+
return;
128+
93129
long targetIndex = (long)(seconds * VoiceChatSettings.SampleRate);
94130
pos = (int)Math.Max(0, Math.Min(targetIndex, data.Length));
95131
}

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
}

0 commit comments

Comments
 (0)