Added audio player logic and tests.
This commit is contained in:
239
Harmonia.Core/Engine/BassAudioEngine.cs
Normal file
239
Harmonia.Core/Engine/BassAudioEngine.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using ManagedBass;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Harmonia.Core.Engine;
|
||||
|
||||
public class BassAudioEngine : IAudioEngine, IDisposable
|
||||
{
|
||||
private readonly BaseMediaPlayer _mediaPlayer;
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
public event EventHandler<PlaybackStoppedEventArgs>? PlaybackStopped;
|
||||
public event EventHandler? StreamFinished;
|
||||
public event EventHandler<PlaybackStateChangedEventArgs>? StateChanged;
|
||||
|
||||
public string? Source { get; private set; }
|
||||
public string[] SupportedFormats { get; }
|
||||
|
||||
public TimeSpan Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return _mediaPlayer.Position;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mediaPlayer.Position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan Length => _mediaPlayer.Duration;
|
||||
|
||||
private float _volume;
|
||||
public float Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return _volume;
|
||||
}
|
||||
set
|
||||
{
|
||||
var newVolume = value;
|
||||
|
||||
if (newVolume > 1.0)
|
||||
newVolume = 1.0f;
|
||||
else if (newVolume < 0.0)
|
||||
newVolume = 0.0f;
|
||||
|
||||
_isMuted = false;
|
||||
_volume = newVolume;
|
||||
_mediaPlayer.Volume = newVolume;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanPause => State == AudioPlaybackState.Playing;
|
||||
|
||||
private bool _isMuted;
|
||||
public bool IsMuted
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isMuted;
|
||||
}
|
||||
set
|
||||
{
|
||||
_isMuted = value;
|
||||
_mediaPlayer.Volume = value ? 0 : Volume;
|
||||
}
|
||||
}
|
||||
|
||||
private AudioPlaybackState _state;
|
||||
public AudioPlaybackState State
|
||||
{
|
||||
get
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
private set
|
||||
{
|
||||
AudioPlaybackState oldValue = _state;
|
||||
_state = value;
|
||||
|
||||
StateChanged?.Invoke(this, new(oldValue, value));
|
||||
}
|
||||
}
|
||||
|
||||
public BassAudioEngine()
|
||||
{
|
||||
_mediaPlayer = new MediaPlayer();
|
||||
_mediaPlayer.MediaLoaded += OnMediaLoaded;
|
||||
_mediaPlayer.MediaFailed += OnPlaybackStopped;
|
||||
_mediaPlayer.MediaEnded += OnStreamFinished;
|
||||
_mediaPlayer.PropertyChanged += OnMediaPlayerPropertyChanged;
|
||||
|
||||
List<string> supportedFormats = [.. Bass.SupportedFormats.Split(';')];
|
||||
supportedFormats.Add(".aac");
|
||||
supportedFormats.Add(".m4a");
|
||||
supportedFormats.Add(".flac");
|
||||
supportedFormats.Add(".opus");
|
||||
supportedFormats.Add(".wma");
|
||||
|
||||
SupportedFormats = [.. supportedFormats];
|
||||
|
||||
IsMuted = false;
|
||||
Volume = 1;
|
||||
}
|
||||
|
||||
public async Task<bool> LoadAsync(string fileName)
|
||||
{
|
||||
if (fileName == Source)
|
||||
{
|
||||
Position = TimeSpan.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (File.Exists(fileName) == false)
|
||||
return false;
|
||||
|
||||
if (await LoadWaveSourceAsync(fileName) == false)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
UpdateSource(fileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> LoadWaveSourceAsync(string fileName)
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
CancellationToken token = _cancellationTokenSource.Token;
|
||||
|
||||
try
|
||||
{
|
||||
await _mediaPlayer.LoadAsync(fileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return false;
|
||||
|
||||
//return new Result(State.Exception, ex.Message);
|
||||
throw new Exception("An error occurred - " + fileName, ex);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateSource(string fileName)
|
||||
{
|
||||
if (State != AudioPlaybackState.Stopped)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
Source = fileName;
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
bool result = _mediaPlayer.Play();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
if (Bass.LastError == Errors.Start)
|
||||
{
|
||||
Bass.Start();
|
||||
_mediaPlayer.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
_mediaPlayer.Pause();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_mediaPlayer.Stop();
|
||||
Position = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
private void OnMediaLoaded(int obj)
|
||||
{
|
||||
if (_mediaPlayer.Volume != Volume)
|
||||
_mediaPlayer.Volume = Volume;
|
||||
}
|
||||
|
||||
private void OnPlaybackStopped(object? sender, EventArgs args)
|
||||
{
|
||||
PlaybackStoppedEventArgs eventArgs = new($"Playback stopped unexpectedly: Last Error = {Bass.LastError}");
|
||||
PlaybackStopped?.Invoke(sender, eventArgs);
|
||||
}
|
||||
|
||||
private void OnStreamFinished(object? sender, EventArgs e)
|
||||
{
|
||||
StreamFinished?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void OnMediaPlayerPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case "State":
|
||||
State = GetPlaybackState(_mediaPlayer.State);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static AudioPlaybackState GetPlaybackState(PlaybackState playbackState)
|
||||
{
|
||||
return playbackState switch
|
||||
{
|
||||
PlaybackState.Stopped => AudioPlaybackState.Stopped,
|
||||
PlaybackState.Playing => AudioPlaybackState.Playing,
|
||||
PlaybackState.Stalled => AudioPlaybackState.Stalled,
|
||||
PlaybackState.Paused => AudioPlaybackState.Paused,
|
||||
_ => throw new Exception($"Unknown audio playback state: {playbackState}")
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mediaPlayer.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user