using ManagedBass; using System.ComponentModel; namespace Harmonia.Core.Engine; public class BassAudioEngine : IAudioEngine, IDisposable { private readonly BaseMediaPlayer _mediaPlayer; private CancellationTokenSource? _cancellationTokenSource; public event EventHandler? PlaybackStopped; public event EventHandler? StreamFinished; public event EventHandler? 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 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 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 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); } }