mirror of
https://github.com/Royce551/FRESHMusicPlayer-Core.git
synced 2025-01-22 10:51:56 -05:00
update backends for new IAudioBackend
This commit is contained in:
parent
40148a8bde
commit
27c441f0d7
12 changed files with 139 additions and 450 deletions
|
@ -4,13 +4,14 @@ using System.Composition.Hosting;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FRESHMusicPlayer.Backends
|
||||
{
|
||||
static class AudioBackendFactory
|
||||
{
|
||||
private static ContainerConfiguration config = new ContainerConfiguration();
|
||||
private static CompositionHost container;
|
||||
private readonly static ContainerConfiguration config = new ContainerConfiguration();
|
||||
private readonly static CompositionHost container;
|
||||
|
||||
private static IEnumerable<Assembly> LoadAssemblies(IEnumerable<string> paths)
|
||||
{
|
||||
|
@ -37,25 +38,9 @@ namespace FRESHMusicPlayer.Backends
|
|||
|
||||
private static void AddDirectory(string path)
|
||||
{
|
||||
//AppDomain.CurrentDomain.AppendPrivatePath(path);
|
||||
try
|
||||
{
|
||||
config.WithAssemblies(LoadAssemblies(Directory.GetFiles(path, "*.dll")));
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine(ex);
|
||||
}
|
||||
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
|
||||
|
||||
config.WithAssemblies(LoadAssemblies(Directory.GetFiles(path, "*.dll")));
|
||||
}
|
||||
|
||||
static AudioBackendFactory()
|
||||
|
@ -66,31 +51,18 @@ namespace FRESHMusicPlayer.Backends
|
|||
container = config.CreateContainer();
|
||||
}
|
||||
|
||||
public static IAudioBackend CreateBackend(string filename)
|
||||
public static async Task<IAudioBackend> CreateBackendAsync(string filename)
|
||||
{
|
||||
var exlist = new List<Exception>();
|
||||
var problems = new List<BackendLoadResult>();
|
||||
foreach (var lazybackend in container.GetExports<Lazy<IAudioBackend>>())
|
||||
{
|
||||
IAudioBackend backend = null;
|
||||
try
|
||||
{
|
||||
backend = lazybackend.Value;
|
||||
backend.LoadSong(filename);
|
||||
return backend;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
backend.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
exlist.Add(ex);
|
||||
}
|
||||
IAudioBackend backend = lazybackend.Value;
|
||||
var result = await backend.LoadSongAsync(filename);
|
||||
|
||||
if (result != BackendLoadResult.OK) problems.Add(result);
|
||||
else return backend;
|
||||
}
|
||||
throw new Exception($"No backend could be found to play {filename}.\n\n{String.Join("\n\n", exlist)}\n");
|
||||
throw new Exception($"A backend couldn't be found to load this file\n{string.Join("\n", problems)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FRESHMusicPlayer.Backends
|
||||
{
|
||||
|
@ -29,6 +30,9 @@ namespace FRESHMusicPlayer.Backends
|
|||
|
||||
public Track ATLTrack { get; set; }
|
||||
|
||||
public FileMetadataProvider(string path) => ATLTrack = new Track(path);
|
||||
private string path;
|
||||
public FileMetadataProvider(string path) => this.path = path;
|
||||
|
||||
public async Task LoadAsync() => await Task.Run(() => ATLTrack = new Track(path));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace FRESHMusicPlayer.Backends
|
|||
int TrackTotal { get; }
|
||||
int DiscNumber { get; }
|
||||
int DiscTotal { get; }
|
||||
|
||||
Task LoadAsync();
|
||||
}
|
||||
|
||||
public enum BackendLoadResult
|
||||
|
@ -41,6 +43,7 @@ namespace FRESHMusicPlayer.Backends
|
|||
OK,
|
||||
NotSupported,
|
||||
Invalid,
|
||||
Corrupt
|
||||
Corrupt,
|
||||
UnknownError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,8 +46,6 @@ namespace FRESHMusicPlayer.Backends
|
|||
|
||||
public async Task<BackendLoadResult> LoadSongAsync(string file)
|
||||
{
|
||||
if (!File.Exists(file)) return BackendLoadResult.Invalid;
|
||||
|
||||
if (AudioFile != null) AudioFile.Dispose();
|
||||
try
|
||||
{
|
||||
|
@ -75,6 +73,11 @@ namespace FRESHMusicPlayer.Backends
|
|||
{
|
||||
return BackendLoadResult.Invalid;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return BackendLoadResult.UnknownError;
|
||||
}
|
||||
return BackendLoadResult.OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Reflection;
|
|||
using FRESHMusicPlayer.Handlers;
|
||||
using FRESHMusicPlayer.Utilities;
|
||||
using FRESHMusicPlayer.Backends;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FRESHMusicPlayer
|
||||
{
|
||||
|
@ -49,7 +50,7 @@ namespace FRESHMusicPlayer
|
|||
/// </summary>
|
||||
public string FilePath { get; private set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Whether the audio backend and file has been loaded and things are ready to go. If you interact with the Player everything
|
||||
/// Whether the audio backend and file has been loaded and things are ready to go. If you interact with the Player when this is false everything
|
||||
/// will explode.
|
||||
/// </summary>
|
||||
public bool FileLoaded { get; set; }
|
||||
|
@ -78,18 +79,18 @@ namespace FRESHMusicPlayer
|
|||
/// <summary>
|
||||
/// Skips to the previous track in the Queue. If there are no tracks for the player to go back to, nothing will happen.
|
||||
/// </summary>
|
||||
public void PreviousSong()
|
||||
public async Task PreviousAsync()
|
||||
{
|
||||
if (Queue.Position <= 1) return;
|
||||
Queue.Position -= 2;
|
||||
PlayMusic();
|
||||
await PlayAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips to the next track in the Queue. If there are no more tracks, the player will stop.
|
||||
/// </summary>
|
||||
/// <param name="avoidNext">Intended to be used only by the player</param>
|
||||
public void NextSong(bool avoidNext = false)
|
||||
public async Task NextAsync(bool avoidNext = false)
|
||||
{
|
||||
AvoidNextQueue = avoidNext;
|
||||
if (Queue.RepeatMode == RepeatMode.RepeatOne) Queue.Position--; // Don't advance Queue, play the same thing again
|
||||
|
@ -99,21 +100,20 @@ namespace FRESHMusicPlayer
|
|||
if (Queue.RepeatMode == RepeatMode.RepeatAll) // Go back to the first track and play it again
|
||||
{
|
||||
Queue.Position = 0;
|
||||
PlayMusic();
|
||||
await PlayAsync();
|
||||
return;
|
||||
}
|
||||
Queue.Clear();
|
||||
StopMusic();
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
PlayMusic();
|
||||
await PlayAsync();
|
||||
}
|
||||
|
||||
// Music Playing Controls
|
||||
private void OnPlaybackStopped(object sender, EventArgs args)
|
||||
private async void OnPlaybackStopped(object sender, EventArgs args)
|
||||
{
|
||||
if (!AvoidNextQueue)
|
||||
NextSong();
|
||||
await NextAsync();
|
||||
else
|
||||
AvoidNextQueue = false;
|
||||
}
|
||||
|
@ -131,23 +131,23 @@ namespace FRESHMusicPlayer
|
|||
/// Plays a track. This is equivalent to calling Queue.Add() and then PlayMusic()./>
|
||||
/// </summary>
|
||||
/// <param name="path">The track to play</param>
|
||||
public void PlayMusic(string path)
|
||||
public async Task PlayMusicAsync(string path)
|
||||
{
|
||||
Queue.Add(path);
|
||||
PlayMusic();
|
||||
await PlayAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Starts playing the Queue. In order to play a track, you must first add it to the Queue using <see cref="AddQueue(string)"/>.
|
||||
/// </summary>
|
||||
/// <param name="repeat">If true, avoids dequeuing the next track. Not to be used for anything other than the player.</param>
|
||||
public void PlayMusic(bool repeat = false)
|
||||
public async Task PlayAsync(bool repeat = false)
|
||||
{
|
||||
if (!repeat && Queue.Queue.Count != 0)
|
||||
FilePath = Queue.Queue[Queue.Position];
|
||||
Queue.Position++;
|
||||
void PMusic()
|
||||
async Task PMusic()
|
||||
{
|
||||
CurrentBackend = AudioBackendFactory.CreateBackend(FilePath);
|
||||
CurrentBackend = await AudioBackendFactory.CreateBackendAsync(FilePath);
|
||||
|
||||
CurrentBackend.Play();
|
||||
CurrentBackend.Volume = Volume;
|
||||
|
@ -160,43 +160,18 @@ namespace FRESHMusicPlayer
|
|||
{
|
||||
if (FileLoaded != true)
|
||||
{
|
||||
PMusic();
|
||||
await PMusic();
|
||||
}
|
||||
else
|
||||
{
|
||||
AvoidNextQueue = true;
|
||||
StopMusic();
|
||||
PMusic();
|
||||
Stop();
|
||||
await PMusic();
|
||||
}
|
||||
|
||||
SongChanged?.Invoke(null,
|
||||
EventArgs.Empty); // Now that playback has started without any issues, fire the song changed event.
|
||||
}
|
||||
//catch (FileNotFoundException) // TODO: move these to NAudioBackend
|
||||
//{
|
||||
// var args = new PlaybackExceptionEventArgs {Details = "That's not a valid file path!"};
|
||||
// SongException?.Invoke(null, args);
|
||||
//}
|
||||
//catch (ArgumentException)
|
||||
//{
|
||||
// var args = new PlaybackExceptionEventArgs {Details = "That's not a valid file path!"};
|
||||
// SongException?.Invoke(null, args);
|
||||
//}
|
||||
//catch (System.Runtime.InteropServices.COMException)
|
||||
//{
|
||||
// var args = new PlaybackExceptionEventArgs {Details = "This isn't a valid audio file!"};
|
||||
// SongException?.Invoke(null, args);
|
||||
//}
|
||||
//catch (FormatException)
|
||||
//{
|
||||
// var args = new PlaybackExceptionEventArgs {Details = "This audio file might be corrupt!"};
|
||||
// SongException?.Invoke(null, args);
|
||||
//}
|
||||
//catch (InvalidOperationException)
|
||||
//{
|
||||
// var args = new PlaybackExceptionEventArgs {Details = "This audio file uses VBR \nor might be corrupt!"};
|
||||
// SongException?.Invoke(null, args);
|
||||
//}
|
||||
catch (Exception e)
|
||||
{
|
||||
var args = new PlaybackExceptionEventArgs(e, $"{e.Message}\n{e.StackTrace}");
|
||||
|
@ -207,7 +182,7 @@ namespace FRESHMusicPlayer
|
|||
/// <summary>
|
||||
/// Completely stops and disposes the player and resets all playback related variables to their defaults.
|
||||
/// </summary>
|
||||
public void StopMusic()
|
||||
public void Stop()
|
||||
{
|
||||
if (!FileLoaded) return;
|
||||
|
||||
|
@ -222,7 +197,7 @@ namespace FRESHMusicPlayer
|
|||
/// <summary>
|
||||
/// Pauses playback without disposing. Can later be resumed with <see cref="ResumeMusic()"/>.
|
||||
/// </summary>
|
||||
public void PauseMusic()
|
||||
public void Pause()
|
||||
{
|
||||
if (!Paused)
|
||||
CurrentBackend?.Pause();
|
||||
|
@ -232,7 +207,7 @@ namespace FRESHMusicPlayer
|
|||
/// <summary>
|
||||
/// Resumes playback.
|
||||
/// </summary>
|
||||
public void ResumeMusic()
|
||||
public void Resume()
|
||||
{
|
||||
if (Paused)
|
||||
CurrentBackend?.Play();
|
||||
|
@ -240,41 +215,5 @@ namespace FRESHMusicPlayer
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Integration
|
||||
|
||||
//#region DiscordRPC // TODO: move this to the frontend
|
||||
///// <summary>
|
||||
///// Initializes the Discord RPC client. Once it has been initialized, you can set the presence by using <see cref="UpdateRPC(string, string, string)"/>
|
||||
///// </summary>
|
||||
///// <param name="applicationID">The application ID of your app</param>
|
||||
//public void InitDiscordRPC(string applicationID)
|
||||
//{ // FMP application ID - 656678380283887626
|
||||
// Client = new DiscordRpcClient(applicationID);
|
||||
|
||||
// Client.OnReady += (sender, e) => { Console.WriteLine("Received Ready from user {0}", e.User.Username); };
|
||||
// Client.OnPresenceUpdate += (sender, e) => { Console.WriteLine("Received Update! {0}", e.Presence); };
|
||||
// Client.Initialize();
|
||||
//}
|
||||
|
||||
//public void UpdateRPC(string Activity, string Artist = null, string Title = null)
|
||||
//{
|
||||
// Client?.SetPresence(new RichPresence()
|
||||
// {
|
||||
// Details = PlayerUtils.TruncateBytes(Title, 120),
|
||||
// State = PlayerUtils.TruncateBytes(Artist, 120),
|
||||
// Assets = new Assets()
|
||||
// {
|
||||
// LargeImageKey = "icon",
|
||||
// SmallImageKey = Activity
|
||||
// },
|
||||
// Timestamps = Timestamps.Now
|
||||
// }
|
||||
// );
|
||||
//}
|
||||
|
||||
//public void DisposeRPC() => Client?.Dispose();
|
||||
|
||||
//#endregion
|
||||
}
|
||||
}
|
|
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("3.0.0.0")]
|
||||
[assembly: AssemblyVersion("4.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("4.0.0.0")]
|
||||
|
|
|
@ -1,291 +0,0 @@
|
|||
using ManagedBass;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FmpBassBackend
|
||||
{
|
||||
/// <summary>
|
||||
/// A modified version of ManagedBass's MediaPlayer that loads songs synchronously
|
||||
/// (FMP.Core currently does not support async loading)
|
||||
/// </summary>
|
||||
public class FMPMediaPlayer : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
#region Fields
|
||||
readonly SynchronizationContext _syncContext;
|
||||
int _handle;
|
||||
|
||||
/// <summary>
|
||||
/// Channel Handle of the loaded audio file.
|
||||
/// </summary>
|
||||
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 FMPMediaPlayer()
|
||||
{
|
||||
var currentDev = Bass.CurrentDevice;
|
||||
|
||||
if (currentDev == -1 || !Bass.GetDeviceInfo(Bass.CurrentDevice).IsInitialized)
|
||||
Bass.Init(currentDev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="MediaPlayer"/>.
|
||||
/// </summary>
|
||||
public FMPMediaPlayer() { _syncContext = SynchronizationContext.Current; }
|
||||
|
||||
#region Events
|
||||
/// <summary>
|
||||
/// Fired when this Channel is Disposed.
|
||||
/// </summary>
|
||||
public event EventHandler Disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the Media Playback Ends
|
||||
/// </summary>
|
||||
public event EventHandler MediaEnded;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the Playback fails
|
||||
/// </summary>
|
||||
public event EventHandler MediaFailed;
|
||||
#endregion
|
||||
|
||||
#region Device
|
||||
int _dev = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the Playback Device used.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the Playback Volume.
|
||||
/// </summary>
|
||||
public double Volume
|
||||
{
|
||||
get => _vol;
|
||||
set
|
||||
{
|
||||
if (!Bass.ChannelSetAttribute(Handle, ChannelAttribute.Volume, value))
|
||||
return;
|
||||
|
||||
_vol = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Override this method for custom loading procedure.
|
||||
/// </summary>
|
||||
/// <param name="FileName">Path to the File to Load.</param>
|
||||
/// <returns><see langword="true"/> on Success, <see langword="false"/> on failure</returns>
|
||||
protected virtual int OnLoad(string FileName) => Bass.CreateStream(FileName);
|
||||
/// <summary>
|
||||
/// Gets the Playback State of the Channel.
|
||||
/// </summary>
|
||||
public PlaybackState State => Handle == 0 ? PlaybackState.Stopped : Bass.ChannelIsActive(Handle);
|
||||
|
||||
#region Playback
|
||||
/// <summary>
|
||||
/// Starts the Channel Playback.
|
||||
/// </summary>
|
||||
public bool Play()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = Bass.ChannelPlay(Handle, _restartOnNextPlayback);
|
||||
|
||||
if (result)
|
||||
_restartOnNextPlayback = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
finally { OnStateChanged(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the Channel Playback.
|
||||
/// </summary>
|
||||
public bool Pause()
|
||||
{
|
||||
try { return Bass.ChannelPause(Handle); }
|
||||
finally { OnStateChanged(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the Channel Playback.
|
||||
/// </summary>
|
||||
/// <remarks>Difference from <see cref="Bass.ChannelStop"/>: Playback is restarted when <see cref="Play"/> is called.</remarks>
|
||||
public bool Stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
_restartOnNextPlayback = true;
|
||||
return Bass.ChannelStop(Handle);
|
||||
}
|
||||
finally { OnStateChanged(); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Playback Duration.
|
||||
/// </summary>
|
||||
public TimeSpan Duration => TimeSpan.FromSeconds(Bass.ChannelBytes2Seconds(Handle, Bass.ChannelGetLength(Handle)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the Playback Position.
|
||||
/// </summary>
|
||||
public TimeSpan Position
|
||||
{
|
||||
get => TimeSpan.FromSeconds(Bass.ChannelBytes2Seconds(Handle, Bass.ChannelGetPosition(Handle)));
|
||||
set => Bass.ChannelSetPosition(Handle, Bass.ChannelSeconds2Bytes(Handle, value.TotalSeconds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a file into the player.
|
||||
/// </summary>
|
||||
/// <param name="FileName">Path to the file to Load.</param>
|
||||
/// <returns><see langword="true"/> on succes, <see langword="false"/> on failure.</returns>
|
||||
public bool Load(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 = OnLoad(FileName);
|
||||
|
||||
if (h == 0)
|
||||
return false;
|
||||
|
||||
Handle = h;
|
||||
|
||||
InitProperties();
|
||||
|
||||
MediaLoaded?.Invoke(h);
|
||||
|
||||
OnPropertyChanged("");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a Media is Loaded.
|
||||
/// </summary>
|
||||
public event Action<int> MediaLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources used by the player.
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Bass.StreamFree(Handle))
|
||||
_handle = 0;
|
||||
}
|
||||
finally { OnStateChanged(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes Properties on every call to <see cref="LoadAsync"/>.
|
||||
/// </summary>
|
||||
protected virtual void InitProperties()
|
||||
{
|
||||
Volume = _vol;
|
||||
}
|
||||
|
||||
void OnStateChanged() => OnPropertyChanged(nameof(State));
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Fires the <see cref="PropertyChanged"/> event.
|
||||
/// </summary>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null)
|
||||
{
|
||||
Action f = () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
|
||||
|
||||
if (_syncContext == null)
|
||||
f();
|
||||
else _syncContext.Post(S => f(), null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FmpBassBackend
|
||||
{
|
||||
|
@ -20,7 +21,9 @@ namespace FmpBassBackend
|
|||
|
||||
public event EventHandler<EventArgs> OnPlaybackStopped;
|
||||
|
||||
private readonly FMPMediaPlayer player = new FMPMediaPlayer();
|
||||
public IMetadataProvider Metadata { get; private set; }
|
||||
|
||||
private readonly MediaPlayer player = new MediaPlayer();
|
||||
|
||||
public FmpBassBackend()
|
||||
{
|
||||
|
@ -28,7 +31,7 @@ namespace FmpBassBackend
|
|||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) // on windows media foundation already provides flac support,
|
||||
{ // don't bother
|
||||
var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
Bass.PluginLoad(Path.Combine(currentDirectory, GetExtensionForCurrentPlatform("libbassflac")));
|
||||
Bass.PluginLoad(Path.Combine(currentDirectory, GetExtensionForCurrentPlatform("bassflac")));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,9 +39,14 @@ namespace FmpBassBackend
|
|||
|
||||
public void Dispose() => player.Dispose();
|
||||
|
||||
public void LoadSong(string file)
|
||||
public async Task<BackendLoadResult> LoadSongAsync(string file)
|
||||
{
|
||||
if (!player.Load(file)) throw new Exception("loading didn't work :("); // not awaited because fmpcore currently does not support await like this
|
||||
var wasSuccessful = await player.LoadAsync(file);
|
||||
|
||||
Metadata = new FileMetadataProvider(file);
|
||||
|
||||
if (!wasSuccessful) return BackendLoadResult.Invalid;
|
||||
else return BackendLoadResult.OK;
|
||||
}
|
||||
|
||||
public void Pause() => player.Pause();
|
||||
|
@ -47,8 +55,8 @@ namespace FmpBassBackend
|
|||
|
||||
private string GetExtensionForCurrentPlatform(string name)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return $"{name}.so";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return $"{name}.dylib";
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return $"lib{name}.so";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return $"lib{name}.dylib";
|
||||
else return $"{name}.dll";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
using CDLib;
|
||||
using FRESHMusicPlayer.Backends;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FmpCdLibBackend
|
||||
{
|
||||
public class CDLibMetadataProvider : IMetadataProvider
|
||||
{
|
||||
public string Title => track.Title;
|
||||
|
||||
public string[] Artists => new string[] { track.Artist };
|
||||
|
||||
public string Album => track.AlbumTitle;
|
||||
|
||||
public byte[] CoverArt => null;
|
||||
|
||||
public string[] Genres => null;
|
||||
|
||||
public int Year => 0;
|
||||
|
||||
public int TrackNumber => (int)track.TrackNumber;
|
||||
|
||||
public int TrackTotal => 0;
|
||||
|
||||
public int DiscNumber => 0;
|
||||
|
||||
public int DiscTotal => 0;
|
||||
|
||||
private readonly IAudioCDTrack track;
|
||||
public CDLibMetadataProvider(IAudioCDTrack track) => this.track = track;
|
||||
|
||||
public Task LoadAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ namespace FmpCdLibBackend
|
|||
|
||||
public float Volume { get => (float)player.Volume; set => player.Volume = value; }
|
||||
|
||||
public IMetadataProvider Metadata { get; private set; }
|
||||
|
||||
public event EventHandler<EventArgs> OnPlaybackStopped;
|
||||
|
||||
public FmpCdLibBackend()
|
||||
|
@ -46,24 +48,31 @@ namespace FmpCdLibBackend
|
|||
//player.Dispose();
|
||||
}
|
||||
|
||||
public void LoadSong(string file)
|
||||
public async Task<BackendLoadResult> LoadSongAsync(string file)
|
||||
{
|
||||
if (Path.GetExtension(file).ToUpper() != ".CDA") throw new Exception("Not a CD");
|
||||
// super hacky; assumes that the path is something like D:\Track01.cda, might be a better way to do this
|
||||
var driveLetter = char.Parse(file.Substring(0, 1));
|
||||
var trackNumber = int.Parse(file.Substring(8, 2));
|
||||
if (Path.GetExtension(file).ToUpper() != ".CDA") return BackendLoadResult.NotSupported;
|
||||
|
||||
var drives = player.GetDrives();
|
||||
foreach (var drive in drives)
|
||||
var result = BackendLoadResult.Invalid;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (drive.DriveLetter == driveLetter)
|
||||
// super hacky; assumes that the path is something like D:\Track01.cda, might be a better way to do this
|
||||
var driveLetter = char.Parse(file.Substring(0, 1));
|
||||
var trackNumber = int.Parse(file.Substring(8, 2));
|
||||
|
||||
var drives = player.GetDrives();
|
||||
foreach (var drive in drives)
|
||||
{
|
||||
var trackToPlay = drive.InsertedMedia.Tracks[trackNumber - 1];
|
||||
TotalTime = trackToPlay.Duration;
|
||||
player.PlayTrack(trackToPlay);
|
||||
return;
|
||||
if (drive.DriveLetter == driveLetter)
|
||||
{
|
||||
var trackToPlay = drive.InsertedMedia.Tracks[trackNumber - 1];
|
||||
TotalTime = trackToPlay.Duration;
|
||||
player.PlayTrack(trackToPlay);
|
||||
Metadata = new CDLibMetadataProvider(trackToPlay);
|
||||
result = BackendLoadResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CDLibMetadataProvider.cs" />
|
||||
<Compile Include="FmpCdLibBackend.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -48,22 +48,22 @@ namespace WinformsTest
|
|||
|
||||
private void button1_Click(object sender, EventArgs e) // pause/resume
|
||||
{
|
||||
if (player.Paused) player.ResumeMusic();
|
||||
else player.PauseMusic();
|
||||
if (player.Paused) player.Resume();
|
||||
else player.Pause();
|
||||
}
|
||||
|
||||
private void button2_Click(object sender, EventArgs e) // stop
|
||||
{
|
||||
player.StopMusic();
|
||||
player.Stop();
|
||||
}
|
||||
|
||||
private void button3_Click(object sender, EventArgs e) // play
|
||||
private async void button3_Click(object sender, EventArgs e) // play
|
||||
{
|
||||
var openFileDialog1 = new OpenFileDialog();
|
||||
if (openFileDialog1.ShowDialog() != DialogResult.OK) return;
|
||||
player.Queue.Add(openFileDialog1.FileName);
|
||||
player.PlayMusic();
|
||||
player.Volume = 0.7f;
|
||||
await player.PlayAsync();
|
||||
player.Volume = 0.5f;
|
||||
}
|
||||
|
||||
private void FreshMusicPlayer_Load(object sender, EventArgs e)
|
||||
|
@ -71,14 +71,14 @@ namespace WinformsTest
|
|||
|
||||
}
|
||||
|
||||
private void button4_Click(object sender, EventArgs e) // next
|
||||
private async void button4_Click(object sender, EventArgs e) // next
|
||||
{
|
||||
player.NextSong();
|
||||
await player.NextAsync();
|
||||
}
|
||||
|
||||
private void button5_Click(object sender, EventArgs e) // previous
|
||||
private async void button5_Click(object sender, EventArgs e) // previous
|
||||
{
|
||||
player.PreviousSong();
|
||||
await player.PreviousAsync();
|
||||
}
|
||||
|
||||
private void button6_Click(object sender, EventArgs e) // extra button 1
|
||||
|
|
Loading…
Reference in a new issue