241 lines
5.6 KiB
C#
241 lines
5.6 KiB
C#
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()
|
|
{
|
|
BassLoader.Initialize();
|
|
|
|
_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);
|
|
}
|
|
} |