226 lines
6.9 KiB
C#
226 lines
6.9 KiB
C#
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<byte>((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<string, List<Tuple<WaveOutEvent, IWaveProvider>>> _activePlayers;
|
|
|
|
public WavPlayer()
|
|
{
|
|
_activePlayers = new ConcurrentDictionary<string, List<Tuple<WaveOutEvent, IWaveProvider>>>();
|
|
}
|
|
|
|
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<Tuple<WaveOutEvent, IWaveProvider>>();
|
|
}
|
|
|
|
_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<WaveOutEvent, IWaveProvider> 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<WaveOutEvent, IWaveProvider>(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();
|
|
}
|
|
}
|