using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.ServiceModel.Security; using System.Threading.Tasks; using BarRaider.SdTools; using NAudio.Wave; using NAudio.Wave.SampleProviders; using Newtonsoft.Json; class CachedSound { public byte[] AudioData { get; private set; } public WaveFormat WaveFormat { get; private set; } public CachedSound(string audioFileName) { using (var audioFileReader = new AudioFileReader(audioFileName)) { // TODO: could add resampling in here if required WaveFormat = audioFileReader.WaveFormat; var wholeFile = new List((int)(audioFileReader.Length)); var readBuffer = new byte[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels*4]; int samplesRead; while ((samplesRead = audioFileReader.Read(readBuffer, 0, readBuffer.Length)) > 0) { wholeFile.AddRange(readBuffer.Take(samplesRead)); } AudioData = wholeFile.ToArray(); } //Logger.Instance.LogMessage(TracingLevel.INFO, $"AudioData Length {AudioData.Length}"); } } class CachedSoundSampleProvider : IWaveProvider { private readonly CachedSound cachedSound; private long position; ~CachedSoundSampleProvider() { //Logger.Instance.LogMessage(TracingLevel.INFO, $"Cache destructor"); } public CachedSoundSampleProvider(CachedSound cachedSound) { this.cachedSound = cachedSound; position = 0; } public int Read(byte[] buffer, int offset, int count) { //Logger.Instance.LogMessage(TracingLevel.INFO, $"Read1 byte"); var availableSamples = cachedSound.AudioData.Length - position; var samplesToCopy = Math.Min(availableSamples, count); try { //Logger.Instance.LogMessage(TracingLevel.INFO, $"{cachedSound.AudioData.GetType()} {buffer.GetType()}"); Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy); } catch (Exception ex) { //Logger.Instance.LogMessage(TracingLevel.INFO, $"{ex.ToString()}"); } //Logger.Instance.LogMessage(TracingLevel.INFO, $"Read3"); position += samplesToCopy; //Logger.Instance.LogMessage(TracingLevel.INFO, $"Sending {samplesToCopy} samples"); return (int)samplesToCopy; } public WaveFormat WaveFormat => cachedSound.WaveFormat; } public class WavPlayer { private static WavPlayer? instance; public static WavPlayer Instance { get { instance ??= new WavPlayer(); return instance; } } public enum PlayMode { PlayOverlap, PlayStop } private readonly ConcurrentDictionary>> _activePlayers; public WavPlayer() { _activePlayers = new ConcurrentDictionary>>(); } public void Play(string filePath, string id, double volume, PlayMode mode) { if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("File path cannot be null or empty.", nameof(filePath)); if (mode == PlayMode.PlayOverlap) { PlayWithOverlap(filePath, id, volume); } else if (mode == PlayMode.PlayStop) { PlayWithStop(filePath, id, volume); } else { throw new ArgumentOutOfRangeException(nameof(mode), "Invalid play mode specified."); } } private void PlayWithOverlap(string filePath, string id, double volume) { try { //Logger.Instance.LogMessage(TracingLevel.INFO, "Play overlap"); var player = CreatePlayer(filePath, id); if (!_activePlayers.ContainsKey(filePath)) { _activePlayers[filePath] = new List>(); } _activePlayers[filePath].Add(player); player.Item1.Volume = (float)volume; player.Item1.Play(); } catch(Exception ex) { //Logger.Instance.LogMessage(TracingLevel.INFO, ex.ToString()); } //var playersToDispose = _activePlayers[filePath].Where(x => x.Item1.PlaybackState == PlaybackState.Stopped).ToList(); //foreach (var p in playersToDispose) //{ // p.Item1.Stop(); // p.Item1.Dispose(); //} //_activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped); } private void PlayWithStop(string filePath, string id, double volume) { if (_activePlayers.TryGetValue(filePath, out var players)) { // Stop and dispose all current players for this file if (players.Any(x => x.Item1.PlaybackState == PlaybackState.Playing)) { var playersToDispose = players.ToList(); foreach (var player in playersToDispose) { player.Item1.Stop(); player.Item1.Dispose(); } } else { PlayWithOverlap(filePath, id, volume); } } else { // Start a new player PlayWithOverlap(filePath, id, volume); } _activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped); } private Tuple CreatePlayer(string filePath, string name) { var reader = new CachedSoundSampleProvider(new CachedSound(filePath)); //var reader = new AudioFileReader(filePath); int number = -1; for (int i = 0; i < WaveOut.DeviceCount; ++i) { if (WaveOut.GetCapabilities(i).ProductName == name) { number = i; } } var player = new WaveOutEvent() { DeviceNumber = number }; player.Init(reader); return new Tuple(player, reader); } private void CleanupPlayer(string filePath) { if (_activePlayers.TryGetValue(filePath, out var players)) { var playersToDispose = players.ToList(); foreach (var p in playersToDispose) { p.Item1.Stop(); p.Item1.Dispose(); } } _activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped); } public void StopAll() { foreach (var players in _activePlayers.Values) { var playersToDispose = players.ToList(); foreach (var player in playersToDispose) { player.Item1.Stop(); player.Item1.Dispose(); } players.Clear(); } _activePlayers.Clear(); } }