using ManagedBass;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Harmonia.Core.Engine;
///
/// A Reusable Channel which can Load files like a Player.
/// is perfect for UIs, as it implements .
/// Also, unlike normal, Properties/Effects set on a persist through subsequent loads.
///
public class BaseMediaPlayer : INotifyPropertyChanged, IDisposable
{
#region Fields
readonly SynchronizationContext? _syncContext;
int _handle;
///
/// Channel Handle of the loaded audio file.
///
protected internal int Handle
{
get => _handle;
private set
{
if (!Bass.ChannelGetInfo(value, out var info))
throw new ArgumentException("Invalid Channel Handle: " + value);
_handle = value;
// Init Events
Bass.ChannelSetSync(Handle, SyncFlags.Free, 0, GetSyncProcedure(() => Disposed?.Invoke(this, EventArgs.Empty)));
Bass.ChannelSetSync(Handle, SyncFlags.Stop, 0, GetSyncProcedure(() => MediaFailed?.Invoke(this, EventArgs.Empty)));
Bass.ChannelSetSync(Handle, SyncFlags.End, 0, GetSyncProcedure(() =>
{
try
{
if (!Bass.ChannelHasFlag(Handle, BassFlags.Loop))
MediaEnded?.Invoke(this, EventArgs.Empty);
}
finally { OnStateChanged(); }
}));
}
}
bool _restartOnNextPlayback;
#endregion
SyncProcedure GetSyncProcedure(Action Handler)
{
return (SyncHandle, Channel, Data, User) =>
{
if (Handler == null)
return;
if (_syncContext == null)
Handler();
else _syncContext.Post(S => Handler(), null);
};
}
static BaseMediaPlayer()
{
var currentDev = Bass.CurrentDevice;
Bass.Configure(Configuration.IncludeDefaultDevice, true);
if (currentDev == -1 || !Bass.GetDeviceInfo(Bass.CurrentDevice).IsInitialized)
Bass.Init(currentDev);
}
///
/// Creates a new instance of .
///
public BaseMediaPlayer() { _syncContext = SynchronizationContext.Current; }
#region Events
///
/// Fired when this Channel is Disposed.
///
public event EventHandler? Disposed;
///
/// Fired when the Media Playback Ends
///
public event EventHandler? MediaEnded;
///
/// Fired when the Playback fails
///
public event EventHandler? MediaFailed;
#endregion
#region Frequency
double _freq = 44100;
///
/// Gets or Sets the Playback Frequency in Hertz.
/// Default is 44100 Hz.
///
public double Frequency
{
get => _freq;
set
{
if (!Bass.ChannelSetAttribute(Handle, ChannelAttribute.Frequency, value))
return;
_freq = value;
OnPropertyChanged();
}
}
#endregion
#region Balance
double _pan;
///
/// Gets or Sets Balance (Panning) (-1 ... 0 ... 1).
/// -1 Represents Completely Left.
/// 1 Represents Completely Right.
/// Default is 0.
///
public double Balance
{
get => _pan;
set
{
if (!Bass.ChannelSetAttribute(Handle, ChannelAttribute.Pan, value))
return;
_pan = value;
OnPropertyChanged();
}
}
#endregion
#region Device
int _dev = -1;
///
/// Gets or Sets the Playback Device used.
///
public int Device
{
get => (_dev = _dev == -1 ? Bass.ChannelGetDevice(Handle) : _dev);
set
{
if (!Bass.GetDeviceInfo(value).IsInitialized)
if (!Bass.Init(value))
return;
if (!Bass.ChannelSetDevice(Handle, value))
return;
_dev = value;
OnPropertyChanged();
}
}
#endregion
#region Volume
double _vol = 0.5;
///
/// Gets or Sets the Playback Volume.
///
public double Volume
{
get => _vol;
set
{
if (!Bass.ChannelSetAttribute(Handle, ChannelAttribute.Volume, value))
return;
_vol = value;
OnPropertyChanged();
}
}
#endregion
#region Loop
bool _loop;
///
/// Gets or Sets whether the Playback is looped.
///
public bool Loop
{
get => _loop;
set
{
if (value ? !Bass.ChannelAddFlag(Handle, BassFlags.Loop) : !Bass.ChannelRemoveFlag(Handle, BassFlags.Loop))
return;
_loop = value;
OnPropertyChanged();
}
}
#endregion
///
/// Override this method for custom loading procedure.
///
/// Path to the File to Load.
/// on Success, on failure
protected virtual int OnLoad(string FileName) => Bass.CreateStream(FileName);
#region Tags
string _title = "", _artist = "", _album = "";
///
/// Title of the Loaded Media.
///
public string Title
{
get => _title;
private set
{
_title = value;
OnPropertyChanged();
}
}
///
/// Artist of the Loaded Media.
///
public string Artist
{
get => _artist;
private set
{
_artist = value;
OnPropertyChanged();
}
}
///
/// Album of the Loaded Media.
///
public string Album
{
get => _album;
private set
{
_album = value;
OnPropertyChanged();
}
}
#endregion
///
/// Gets the Playback State of the Channel.
///
public PlaybackState State => Handle == 0 ? PlaybackState.Stopped : Bass.ChannelIsActive(Handle);
#region Playback
///
/// Starts the Channel Playback.
///
public bool Play()
{
try
{
var result = Bass.ChannelPlay(Handle, _restartOnNextPlayback);
if (result)
_restartOnNextPlayback = false;
return result;
}
finally { OnStateChanged(); }
}
///
/// Pauses the Channel Playback.
///
public bool Pause()
{
try { return Bass.ChannelPause(Handle); }
finally { OnStateChanged(); }
}
///
/// Stops the Channel Playback.
///
/// Difference from : Playback is restarted when is called.
public bool Stop()
{
try
{
_restartOnNextPlayback = true;
return Bass.ChannelStop(Handle);
}
finally { OnStateChanged(); }
}
#endregion
///
/// Gets the Playback Duration.
///
public TimeSpan Duration => TimeSpan.FromSeconds(Bass.ChannelBytes2Seconds(Handle, Bass.ChannelGetLength(Handle)));
///
/// Gets or Sets the Playback Position.
///
public TimeSpan Position
{
get => TimeSpan.FromSeconds(Bass.ChannelBytes2Seconds(Handle, Bass.ChannelGetPosition(Handle)));
set => Bass.ChannelSetPosition(Handle, Bass.ChannelSeconds2Bytes(Handle, value.TotalSeconds));
}
///
/// Loads a file into the player.
///
/// Path to the file to Load.
/// on succes, on failure.
public async Task LoadAsync(string FileName)
{
try
{
if (Handle != 0)
Bass.StreamFree(Handle);
}
catch { }
if (_dev != -1)
Bass.CurrentDevice = _dev;
var currentDev = Bass.CurrentDevice;
if (currentDev == -1 || !Bass.GetDeviceInfo(Bass.CurrentDevice).IsInitialized)
Bass.Init(currentDev);
var h = await Task.Run(() => OnLoad(FileName));
if (h == 0)
return false;
Handle = h;
// Tag reading logic can cause exceptions
//var tags = TagReader.Read(Handle);
//Title = !string.IsNullOrWhiteSpace(tags.Title) ? tags.Title
// : Path.GetFileNameWithoutExtension(FileName);
//Artist = tags.Artist;
//Album = tags.Album;
InitProperties();
MediaLoaded?.Invoke(h);
OnPropertyChanged("");
return true;
}
///
/// Fired when a Media is Loaded.
///
public event Action? MediaLoaded;
///
/// Frees all resources used by the player.
///
public virtual void Dispose()
{
try
{
if (Bass.StreamFree(Handle))
_handle = 0;
}
finally
{
OnStateChanged();
GC.SuppressFinalize(this);
}
}
///
/// Initializes Properties on every call to .
///
protected virtual void InitProperties()
{
Frequency = _freq;
Balance = _pan;
Volume = _vol;
Loop = _loop;
}
void OnStateChanged() => OnPropertyChanged(nameof(State));
///
/// Fired when a property value changes.
///
public event PropertyChangedEventHandler? PropertyChanged;
///
/// Fires the event.
///
protected virtual void OnPropertyChanged([CallerMemberName] string? PropertyName = null)
{
void f() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
if (_syncContext == null)
f();
else _syncContext.Post(S => f(), null);
}
}