From 585508d17b02863930bd82832530d63b8bde3419 Mon Sep 17 00:00:00 2001 From: ERmak148 Date: Mon, 6 Oct 2025 19:21:20 +0400 Subject: [PATCH 1/4] Added exception handling in monitoring coroutine --- Pooling/PooledAudioPlayer.cs | 55 ++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/Pooling/PooledAudioPlayer.cs b/Pooling/PooledAudioPlayer.cs index 096d6e8..097f982 100644 --- a/Pooling/PooledAudioPlayer.cs +++ b/Pooling/PooledAudioPlayer.cs @@ -116,41 +116,48 @@ public void ReturnDelWhenAllClipsPlayed() private System.Collections.IEnumerator MonitorClipsAndReturn(bool destroy) { - while (Player.ClipsById.Count > 0) + try { - bool allClipsWillEnd = true; - foreach (var clip in Player.ClipsById.Values) + while (Player.ClipsById.Count > 0) { - if (clip.Loop && clip.DestroyOnEnd == false) + bool allClipsWillEnd = true; + foreach (var clip in Player.ClipsById.Values) { - allClipsWillEnd = false; - break; + if (clip.Loop && clip.DestroyOnEnd == false) + { + allClipsWillEnd = false; + break; + } } - } - - if (!allClipsWillEnd) - { - foreach (var clip in Player.ClipsById.Values.ToList()) + + if (!allClipsWillEnd) { - if (clip.Loop) + foreach (var clip in Player.ClipsById.Values.ToList()) { - clip.Loop = false; + if (clip.Loop) + { + clip.Loop = false; + } } } + + yield return new WaitForSeconds(0.5f); + } + + _isReturning = false; + + if (destroy) + { + ReturnDel(); + } + else + { + Return(); } - - yield return new WaitForSeconds(0.1f); - } - - _isReturning = false; - - if (destroy) - { - ReturnDel(); } - else + finally { - Return(); + _isReturning = false; } } From a1bc39cac8b0ea75d032119d6eebab898a68bb50 Mon Sep 17 00:00:00 2001 From: ERmak148 Date: Mon, 6 Oct 2025 19:26:58 +0400 Subject: [PATCH 2/4] made constant static and added isReturning in Return method --- Pooling/PooledAudioPlayer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Pooling/PooledAudioPlayer.cs b/Pooling/PooledAudioPlayer.cs index 097f982..396b363 100644 --- a/Pooling/PooledAudioPlayer.cs +++ b/Pooling/PooledAudioPlayer.cs @@ -5,7 +5,7 @@ public class PooledAudioPlayer { private readonly AudioPlayerPool _pool; private readonly string _internalName; - private Vector3 _hiddenPosition = new Vector3(999, 999, 999); + private static readonly Vector3 _hiddenPosition = new Vector3(999, 999, 999); private bool _isReturning = false; public AudioPlayer Player { get; private set; } @@ -68,6 +68,7 @@ internal void Deactivate() public void Return() { if (_isReturning) return; + _isReturning = true; _pool.Return(this); } @@ -132,7 +133,7 @@ private System.Collections.IEnumerator MonitorClipsAndReturn(bool destroy) if (!allClipsWillEnd) { - foreach (var clip in Player.ClipsById.Values.ToList()) + foreach (var clip in Player.ClipsById.Values) { if (clip.Loop) { From 49bfb2a1783b1a44f56f3995cdfa741771c50fb7 Mon Sep 17 00:00:00 2001 From: ERmak148 Date: Mon, 6 Oct 2025 20:01:56 +0400 Subject: [PATCH 3/4] Added any file read ability --- AudioFileLoader.cs | 80 ++++++++++++++++++++++++++++++++++++++ Models/AudioClipStorage.cs | 45 +++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 AudioFileLoader.cs diff --git a/AudioFileLoader.cs b/AudioFileLoader.cs new file mode 100644 index 0000000..8898830 --- /dev/null +++ b/AudioFileLoader.cs @@ -0,0 +1,80 @@ +using System.Diagnostics; +using System.Threading.Tasks; + +namespace AudioPlayerApi; + +public static class AudioFileLoader +{ + public static float[] LoadAudioFile(string filePath, int targetSampleRate = 48000, int targetChannels = 1) + { + if (!File.Exists(filePath)) + { + ServerConsole.AddLog($"[AudioFileLoader] File not found: {filePath}"); + return Array.Empty(); + } + + Task.Run(async () => await Ffmpeg.InitializeFfmpegAsync()).GetAwaiter().GetResult(); + + try + { + return ConvertToRawPcm(filePath, targetSampleRate, targetChannels); + } + catch (Exception ex) + { + ServerConsole.AddLog($"[AudioFileLoader] Error loading audio file {filePath}: {ex}"); + return Array.Empty(); + } + } + + // this method I writed with gpt because I am dumb and can't understand simple things + private static float[] ConvertToRawPcm(string filePath, int sampleRate, int channels) + { + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = Ffmpeg.FfmpegPath, + Arguments = $"-hide_banner -loglevel error -i \"{filePath}\" -vn -ac {channels} -ar {sampleRate} -f f32le pipe:1", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using (Process process = Process.Start(psi)) + { + if (process == null) + { + ServerConsole.AddLog("[AudioFileLoader] Failed to start FFmpeg process"); + return Array.Empty(); + } + + var errorTask = process.StandardError.ReadToEndAsync(); + + using (MemoryStream ms = new MemoryStream()) + { + process.StandardOutput.BaseStream.CopyTo(ms); + process.WaitForExit(); + + string errors = errorTask.Result; + if (!string.IsNullOrEmpty(errors)) + { + ServerConsole.AddLog($"[AudioFileLoader] FFmpeg warnings/errors: {errors}"); + } + + if (process.ExitCode != 0) + { + ServerConsole.AddLog($"[AudioFileLoader] FFmpeg exited with code {process.ExitCode}"); + return Array.Empty(); + } + + byte[] pcmBytes = ms.ToArray(); + int sampleCount = pcmBytes.Length / 4; + float[] samples = new float[sampleCount]; + Buffer.BlockCopy(pcmBytes, 0, samples, 0, pcmBytes.Length); + + ServerConsole.AddLog($"[AudioFileLoader] Loaded {filePath}: {sampleCount} samples, {sampleCount / (float)(sampleRate * channels):F2}s duration"); + + return samples; + } + } + } +} \ No newline at end of file diff --git a/Models/AudioClipStorage.cs b/Models/AudioClipStorage.cs index 4312282..3ed6886 100644 --- a/Models/AudioClipStorage.cs +++ b/Models/AudioClipStorage.cs @@ -1,5 +1,6 @@ using System.IO; using System.Reflection; +using AudioPlayerApi; /// /// Manages the storage and loading of audio clips for playback. @@ -121,6 +122,50 @@ public static bool LoadClip(string path, string name = null) AudioClips.Add(name, new AudioClipData(name, sampleRate, channels, samples)); return true; } + /// + /// Loads an audio file, converts it to PCM, and adds it to the clip storage and player. + /// + /// The AudioPlayer instance + /// Path to the audio file + /// Name to assign to the clip (if null, uses filename) + /// Playback volume + /// Whether to loop the clip + /// Whether to destroy after playback + /// AudioClipPlayback instance or null if loading failed + public static bool LoadClipAny( + string filePath, + string clipName) + { + if (string.IsNullOrEmpty(clipName)) + clipName = Path.GetFileNameWithoutExtension(filePath); + + EnsureFfmpegInitialized(); + + float[] samples = AudioFileLoader.LoadAudioFile( + filePath, + AudioClipPlayback.SamplingRate, + AudioClipPlayback.Channels); + + if (samples.Length == 0) + { + ServerConsole.AddLog($"[AudioPlayer] Failed to load audio file: {filePath}"); + return false; + } + + if (!AudioClips.ContainsKey(clipName)) + { + AudioClips[clipName] = new AudioClipData(clipName, AudioClipPlayback.SamplingRate, + AudioClipPlayback.Channels, samples); + ServerConsole.AddLog($"[AudioPlayer] Added clip '{clipName}' to storage"); + } + + return true; + } + private static void EnsureFfmpegInitialized() + { + if (!File.Exists(Ffmpeg.FfmpegPath)) + throw new InvalidOperationException("FFmpeg not initialized. Call InitializeFfmpegAsync at startup."); + } /// /// Destroys loaded clips. From 344756470facd05b1f9b1c576cf9af4be4f38223 Mon Sep 17 00:00:00 2001 From: ERmak148 Date: Mon, 6 Oct 2025 20:37:35 +0400 Subject: [PATCH 4/4] xd (i just added "killer feature" for ffmpeg initialization. I dont know why author used Tasks, but I'm not use it --- AudioFileLoader.cs | 60 +++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/AudioFileLoader.cs b/AudioFileLoader.cs index 8898830..37db46a 100644 --- a/AudioFileLoader.cs +++ b/AudioFileLoader.cs @@ -13,7 +13,8 @@ public static float[] LoadAudioFile(string filePath, int targetSampleRate = 4800 return Array.Empty(); } - Task.Run(async () => await Ffmpeg.InitializeFfmpegAsync()).GetAwaiter().GetResult(); + // I think this line can kill the server, but i idk how to check ffmpeg. On server start mb + Ffmpeg.InitializeFfmpegAsync().GetAwaiter().GetResult(); try { @@ -26,55 +27,60 @@ public static float[] LoadAudioFile(string filePath, int targetSampleRate = 4800 } } + // this method I writed with gpt because I am dumb and can't understand simple things private static float[] ConvertToRawPcm(string filePath, int sampleRate, int channels) { - ProcessStartInfo psi = new ProcessStartInfo + var psi = new ProcessStartInfo { FileName = Ffmpeg.FfmpegPath, - Arguments = $"-hide_banner -loglevel error -i \"{filePath}\" -vn -ac {channels} -ar {sampleRate} -f f32le pipe:1", + Arguments = $"-hide_banner -nostats -loglevel error -i \"{filePath}\" -vn -ac {channels} -ar {sampleRate} -f f32le pipe:1", RedirectStandardOutput = true, - RedirectStandardError = true, + RedirectStandardError = false, UseShellExecute = false, CreateNoWindow = true }; - using (Process process = Process.Start(psi)) + using var process = new Process { StartInfo = psi }; + try { - if (process == null) + if (!process.Start()) { ServerConsole.AddLog("[AudioFileLoader] Failed to start FFmpeg process"); return Array.Empty(); } - - var errorTask = process.StandardError.ReadToEndAsync(); - - using (MemoryStream ms = new MemoryStream()) + + byte[] pcmBytes; + using (var ms = new MemoryStream()) { process.StandardOutput.BaseStream.CopyTo(ms); - process.WaitForExit(); - - string errors = errorTask.Result; - if (!string.IsNullOrEmpty(errors)) + if (!process.WaitForExit(10000)) { - ServerConsole.AddLog($"[AudioFileLoader] FFmpeg warnings/errors: {errors}"); - } - - if (process.ExitCode != 0) - { - ServerConsole.AddLog($"[AudioFileLoader] FFmpeg exited with code {process.ExitCode}"); + try { process.Kill(); } catch { /* ignore */ } + ServerConsole.AddLog("[AudioFileLoader] FFmpeg process timeout"); return Array.Empty(); } - byte[] pcmBytes = ms.ToArray(); - int sampleCount = pcmBytes.Length / 4; - float[] samples = new float[sampleCount]; - Buffer.BlockCopy(pcmBytes, 0, samples, 0, pcmBytes.Length); + pcmBytes = ms.ToArray(); + } - ServerConsole.AddLog($"[AudioFileLoader] Loaded {filePath}: {sampleCount} samples, {sampleCount / (float)(sampleRate * channels):F2}s duration"); - - return samples; + if (process.ExitCode != 0) + { + ServerConsole.AddLog($"[AudioFileLoader] FFmpeg exited with code {process.ExitCode}"); + return Array.Empty(); } + + int sampleCount = pcmBytes.Length / 4; + var samples = new float[sampleCount]; + Buffer.BlockCopy(pcmBytes, 0, samples, 0, pcmBytes.Length); + + ServerConsole.AddLog($"[AudioFileLoader] Loaded {filePath}: {sampleCount} samples, {sampleCount / (float)(sampleRate * channels):F2}s duration"); + return samples; + } + catch + { + try { if (!process.HasExited) process.Kill(); } catch { } + throw; } } } \ No newline at end of file