mirror of
https://github.com/Royce551/FRESHMusicPlayer-Core.git
synced 2025-01-22 19:02:35 -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.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FRESHMusicPlayer.Backends
|
namespace FRESHMusicPlayer.Backends
|
||||||
{
|
{
|
||||||
static class AudioBackendFactory
|
static class AudioBackendFactory
|
||||||
{
|
{
|
||||||
private static ContainerConfiguration config = new ContainerConfiguration();
|
private readonly static ContainerConfiguration config = new ContainerConfiguration();
|
||||||
private static CompositionHost container;
|
private readonly static CompositionHost container;
|
||||||
|
|
||||||
private static IEnumerable<Assembly> LoadAssemblies(IEnumerable<string> paths)
|
private static IEnumerable<Assembly> LoadAssemblies(IEnumerable<string> paths)
|
||||||
{
|
{
|
||||||
|
@ -37,26 +38,10 @@ namespace FRESHMusicPlayer.Backends
|
||||||
|
|
||||||
private static void AddDirectory(string path)
|
private static void AddDirectory(string path)
|
||||||
{
|
{
|
||||||
//AppDomain.CurrentDomain.AppendPrivatePath(path);
|
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
|
||||||
try
|
|
||||||
{
|
|
||||||
config.WithAssemblies(LoadAssemblies(Directory.GetFiles(path, "*.dll")));
|
config.WithAssemblies(LoadAssemblies(Directory.GetFiles(path, "*.dll")));
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(path);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.WriteLine(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static AudioBackendFactory()
|
static AudioBackendFactory()
|
||||||
{
|
{
|
||||||
|
@ -66,31 +51,18 @@ namespace FRESHMusicPlayer.Backends
|
||||||
container = config.CreateContainer();
|
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>>())
|
foreach (var lazybackend in container.GetExports<Lazy<IAudioBackend>>())
|
||||||
{
|
{
|
||||||
IAudioBackend backend = null;
|
IAudioBackend backend = lazybackend.Value;
|
||||||
try
|
var result = await backend.LoadSongAsync(filename);
|
||||||
{
|
|
||||||
backend = lazybackend.Value;
|
if (result != BackendLoadResult.OK) problems.Add(result);
|
||||||
backend.LoadSong(filename);
|
else return backend;
|
||||||
return backend;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
throw new Exception($"A backend couldn't be found to load this file\n{string.Join("\n", problems)}");
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
backend.Dispose();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
exlist.Add(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Exception($"No backend could be found to play {filename}.\n\n{String.Join("\n\n", exlist)}\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FRESHMusicPlayer.Backends
|
namespace FRESHMusicPlayer.Backends
|
||||||
{
|
{
|
||||||
|
@ -29,6 +30,9 @@ namespace FRESHMusicPlayer.Backends
|
||||||
|
|
||||||
public Track ATLTrack { get; set; }
|
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 TrackTotal { get; }
|
||||||
int DiscNumber { get; }
|
int DiscNumber { get; }
|
||||||
int DiscTotal { get; }
|
int DiscTotal { get; }
|
||||||
|
|
||||||
|
Task LoadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BackendLoadResult
|
public enum BackendLoadResult
|
||||||
|
@ -41,6 +43,7 @@ namespace FRESHMusicPlayer.Backends
|
||||||
OK,
|
OK,
|
||||||
NotSupported,
|
NotSupported,
|
||||||
Invalid,
|
Invalid,
|
||||||
Corrupt
|
Corrupt,
|
||||||
|
UnknownError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,8 +46,6 @@ namespace FRESHMusicPlayer.Backends
|
||||||
|
|
||||||
public async Task<BackendLoadResult> LoadSongAsync(string file)
|
public async Task<BackendLoadResult> LoadSongAsync(string file)
|
||||||
{
|
{
|
||||||
if (!File.Exists(file)) return BackendLoadResult.Invalid;
|
|
||||||
|
|
||||||
if (AudioFile != null) AudioFile.Dispose();
|
if (AudioFile != null) AudioFile.Dispose();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -75,6 +73,11 @@ namespace FRESHMusicPlayer.Backends
|
||||||
{
|
{
|
||||||
return BackendLoadResult.Invalid;
|
return BackendLoadResult.Invalid;
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
return BackendLoadResult.UnknownError;
|
||||||
|
}
|
||||||
return BackendLoadResult.OK;
|
return BackendLoadResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Reflection;
|
||||||
using FRESHMusicPlayer.Handlers;
|
using FRESHMusicPlayer.Handlers;
|
||||||
using FRESHMusicPlayer.Utilities;
|
using FRESHMusicPlayer.Utilities;
|
||||||
using FRESHMusicPlayer.Backends;
|
using FRESHMusicPlayer.Backends;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FRESHMusicPlayer
|
namespace FRESHMusicPlayer
|
||||||
{
|
{
|
||||||
|
@ -49,7 +50,7 @@ namespace FRESHMusicPlayer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FilePath { get; private set; } = string.Empty;
|
public string FilePath { get; private set; } = string.Empty;
|
||||||
/// <summary>
|
/// <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.
|
/// will explode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool FileLoaded { get; set; }
|
public bool FileLoaded { get; set; }
|
||||||
|
@ -78,18 +79,18 @@ namespace FRESHMusicPlayer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Skips to the previous track in the Queue. If there are no tracks for the player to go back to, nothing will happen.
|
/// Skips to the previous track in the Queue. If there are no tracks for the player to go back to, nothing will happen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PreviousSong()
|
public async Task PreviousAsync()
|
||||||
{
|
{
|
||||||
if (Queue.Position <= 1) return;
|
if (Queue.Position <= 1) return;
|
||||||
Queue.Position -= 2;
|
Queue.Position -= 2;
|
||||||
PlayMusic();
|
await PlayAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Skips to the next track in the Queue. If there are no more tracks, the player will stop.
|
/// Skips to the next track in the Queue. If there are no more tracks, the player will stop.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="avoidNext">Intended to be used only by the player</param>
|
/// <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;
|
AvoidNextQueue = avoidNext;
|
||||||
if (Queue.RepeatMode == RepeatMode.RepeatOne) Queue.Position--; // Don't advance Queue, play the same thing again
|
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
|
if (Queue.RepeatMode == RepeatMode.RepeatAll) // Go back to the first track and play it again
|
||||||
{
|
{
|
||||||
Queue.Position = 0;
|
Queue.Position = 0;
|
||||||
PlayMusic();
|
await PlayAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Queue.Clear();
|
Stop();
|
||||||
StopMusic();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PlayMusic();
|
await PlayAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Music Playing Controls
|
// Music Playing Controls
|
||||||
private void OnPlaybackStopped(object sender, EventArgs args)
|
private async void OnPlaybackStopped(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
if (!AvoidNextQueue)
|
if (!AvoidNextQueue)
|
||||||
NextSong();
|
await NextAsync();
|
||||||
else
|
else
|
||||||
AvoidNextQueue = false;
|
AvoidNextQueue = false;
|
||||||
}
|
}
|
||||||
|
@ -131,23 +131,23 @@ namespace FRESHMusicPlayer
|
||||||
/// Plays a track. This is equivalent to calling Queue.Add() and then PlayMusic()./>
|
/// Plays a track. This is equivalent to calling Queue.Add() and then PlayMusic()./>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The track to play</param>
|
/// <param name="path">The track to play</param>
|
||||||
public void PlayMusic(string path)
|
public async Task PlayMusicAsync(string path)
|
||||||
{
|
{
|
||||||
Queue.Add(path);
|
Queue.Add(path);
|
||||||
PlayMusic();
|
await PlayAsync();
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts playing the Queue. In order to play a track, you must first add it to the Queue using <see cref="AddQueue(string)"/>.
|
/// Starts playing the Queue. In order to play a track, you must first add it to the Queue using <see cref="AddQueue(string)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="repeat">If true, avoids dequeuing the next track. Not to be used for anything other than the player.</param>
|
/// <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)
|
if (!repeat && Queue.Queue.Count != 0)
|
||||||
FilePath = Queue.Queue[Queue.Position];
|
FilePath = Queue.Queue[Queue.Position];
|
||||||
Queue.Position++;
|
Queue.Position++;
|
||||||
void PMusic()
|
async Task PMusic()
|
||||||
{
|
{
|
||||||
CurrentBackend = AudioBackendFactory.CreateBackend(FilePath);
|
CurrentBackend = await AudioBackendFactory.CreateBackendAsync(FilePath);
|
||||||
|
|
||||||
CurrentBackend.Play();
|
CurrentBackend.Play();
|
||||||
CurrentBackend.Volume = Volume;
|
CurrentBackend.Volume = Volume;
|
||||||
|
@ -160,43 +160,18 @@ namespace FRESHMusicPlayer
|
||||||
{
|
{
|
||||||
if (FileLoaded != true)
|
if (FileLoaded != true)
|
||||||
{
|
{
|
||||||
PMusic();
|
await PMusic();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AvoidNextQueue = true;
|
AvoidNextQueue = true;
|
||||||
StopMusic();
|
Stop();
|
||||||
PMusic();
|
await PMusic();
|
||||||
}
|
}
|
||||||
|
|
||||||
SongChanged?.Invoke(null,
|
SongChanged?.Invoke(null,
|
||||||
EventArgs.Empty); // Now that playback has started without any issues, fire the song changed event.
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var args = new PlaybackExceptionEventArgs(e, $"{e.Message}\n{e.StackTrace}");
|
var args = new PlaybackExceptionEventArgs(e, $"{e.Message}\n{e.StackTrace}");
|
||||||
|
@ -207,7 +182,7 @@ namespace FRESHMusicPlayer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Completely stops and disposes the player and resets all playback related variables to their defaults.
|
/// Completely stops and disposes the player and resets all playback related variables to their defaults.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopMusic()
|
public void Stop()
|
||||||
{
|
{
|
||||||
if (!FileLoaded) return;
|
if (!FileLoaded) return;
|
||||||
|
|
||||||
|
@ -222,7 +197,7 @@ namespace FRESHMusicPlayer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pauses playback without disposing. Can later be resumed with <see cref="ResumeMusic()"/>.
|
/// Pauses playback without disposing. Can later be resumed with <see cref="ResumeMusic()"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PauseMusic()
|
public void Pause()
|
||||||
{
|
{
|
||||||
if (!Paused)
|
if (!Paused)
|
||||||
CurrentBackend?.Pause();
|
CurrentBackend?.Pause();
|
||||||
|
@ -232,7 +207,7 @@ namespace FRESHMusicPlayer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resumes playback.
|
/// Resumes playback.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ResumeMusic()
|
public void Resume()
|
||||||
{
|
{
|
||||||
if (Paused)
|
if (Paused)
|
||||||
CurrentBackend?.Play();
|
CurrentBackend?.Play();
|
||||||
|
@ -240,41 +215,5 @@ namespace FRESHMusicPlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("3.0.0.0")]
|
[assembly: AssemblyVersion("4.0.0.0")]
|
||||||
[assembly: AssemblyFileVersion("3.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.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FmpBassBackend
|
namespace FmpBassBackend
|
||||||
{
|
{
|
||||||
|
@ -20,7 +21,9 @@ namespace FmpBassBackend
|
||||||
|
|
||||||
public event EventHandler<EventArgs> OnPlaybackStopped;
|
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()
|
public FmpBassBackend()
|
||||||
{
|
{
|
||||||
|
@ -28,7 +31,7 @@ namespace FmpBassBackend
|
||||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) // on windows media foundation already provides flac support,
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) // on windows media foundation already provides flac support,
|
||||||
{ // don't bother
|
{ // don't bother
|
||||||
var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
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 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();
|
public void Pause() => player.Pause();
|
||||||
|
@ -47,8 +55,8 @@ namespace FmpBassBackend
|
||||||
|
|
||||||
private string GetExtensionForCurrentPlatform(string name)
|
private string GetExtensionForCurrentPlatform(string name)
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return $"{name}.so";
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return $"lib{name}.so";
|
||||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return $"{name}.dylib";
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return $"lib{name}.dylib";
|
||||||
else return $"{name}.dll";
|
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 float Volume { get => (float)player.Volume; set => player.Volume = value; }
|
||||||
|
|
||||||
|
public IMetadataProvider Metadata { get; private set; }
|
||||||
|
|
||||||
public event EventHandler<EventArgs> OnPlaybackStopped;
|
public event EventHandler<EventArgs> OnPlaybackStopped;
|
||||||
|
|
||||||
public FmpCdLibBackend()
|
public FmpCdLibBackend()
|
||||||
|
@ -46,9 +48,13 @@ namespace FmpCdLibBackend
|
||||||
//player.Dispose();
|
//player.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadSong(string file)
|
public async Task<BackendLoadResult> LoadSongAsync(string file)
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(file).ToUpper() != ".CDA") return BackendLoadResult.NotSupported;
|
||||||
|
|
||||||
|
var result = BackendLoadResult.Invalid;
|
||||||
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
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
|
// 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 driveLetter = char.Parse(file.Substring(0, 1));
|
||||||
var trackNumber = int.Parse(file.Substring(8, 2));
|
var trackNumber = int.Parse(file.Substring(8, 2));
|
||||||
|
@ -61,9 +67,12 @@ namespace FmpCdLibBackend
|
||||||
var trackToPlay = drive.InsertedMedia.Tracks[trackNumber - 1];
|
var trackToPlay = drive.InsertedMedia.Tracks[trackNumber - 1];
|
||||||
TotalTime = trackToPlay.Duration;
|
TotalTime = trackToPlay.Duration;
|
||||||
player.PlayTrack(trackToPlay);
|
player.PlayTrack(trackToPlay);
|
||||||
return;
|
Metadata = new CDLibMetadataProvider(trackToPlay);
|
||||||
|
result = BackendLoadResult.OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Pause()
|
public void Pause()
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="CDLibMetadataProvider.cs" />
|
||||||
<Compile Include="FmpCdLibBackend.cs" />
|
<Compile Include="FmpCdLibBackend.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -48,22 +48,22 @@ namespace WinformsTest
|
||||||
|
|
||||||
private void button1_Click(object sender, EventArgs e) // pause/resume
|
private void button1_Click(object sender, EventArgs e) // pause/resume
|
||||||
{
|
{
|
||||||
if (player.Paused) player.ResumeMusic();
|
if (player.Paused) player.Resume();
|
||||||
else player.PauseMusic();
|
else player.Pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void button2_Click(object sender, EventArgs e) // stop
|
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();
|
var openFileDialog1 = new OpenFileDialog();
|
||||||
if (openFileDialog1.ShowDialog() != DialogResult.OK) return;
|
if (openFileDialog1.ShowDialog() != DialogResult.OK) return;
|
||||||
player.Queue.Add(openFileDialog1.FileName);
|
player.Queue.Add(openFileDialog1.FileName);
|
||||||
player.PlayMusic();
|
await player.PlayAsync();
|
||||||
player.Volume = 0.7f;
|
player.Volume = 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FreshMusicPlayer_Load(object sender, EventArgs e)
|
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
|
private void button6_Click(object sender, EventArgs e) // extra button 1
|
||||||
|
|
Loading…
Reference in a new issue