Merge pull request #8 from Royce551/fmpcore3

FMP Core 3
This commit is contained in:
Squid Grill 2021-03-18 21:38:19 -05:00 committed by GitHub
commit f0f522c331
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 679 additions and 349 deletions

View file

@ -3,11 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FRESHMusicPlayer.Core", "FRESHMusicPlayer.Player\FRESHMusicPlayer.Core.csproj", "{FFE8EAFD-7FAA-4503-9364-2643821A2BC8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FRESHMusicPlayer.Core", "FRESHMusicPlayer.Player\FRESHMusicPlayer.Core.csproj", "{FFE8EAFD-7FAA-4503-9364-2643821A2BC8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinformsTest", "WinformsTest\WinformsTest.csproj", "{AA375910-6E80-4570-9663-8EA13238192A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinformsTest", "WinformsTest\WinformsTest.csproj", "{AA375910-6E80-4570-9663-8EA13238192A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GtkSharpTest", "GtkSharpTest\GtkSharpTest.csproj", "{8FE8EBF4-7E03-47B5-86AF-0DEA77260F24}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GtkSharpTest", "GtkSharpTest\GtkSharpTest.csproj", "{8FE8EBF4-7E03-47B5-86AF-0DEA77260F24}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FmpCdLibBackend", "FmpCdLibBackend\FmpCdLibBackend.csproj", "{31D77A1D-F161-42E8-826E-3E27E6566B28}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -27,6 +29,10 @@ Global
{8FE8EBF4-7E03-47B5-86AF-0DEA77260F24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FE8EBF4-7E03-47B5-86AF-0DEA77260F24}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FE8EBF4-7E03-47B5-86AF-0DEA77260F24}.Release|Any CPU.Build.0 = Release|Any CPU
{31D77A1D-F161-42E8-826E-3E27E6566B28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31D77A1D-F161-42E8-826E-3E27E6566B28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31D77A1D-F161-42E8-826E-3E27E6566B28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31D77A1D-F161-42E8-826E-3E27E6566B28}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -12,8 +12,6 @@ namespace FRESHMusicPlayer.Backends
private static ContainerConfiguration config = new ContainerConfiguration();
private static CompositionHost container;
private static List<string> directories = new List<string>();
private static IEnumerable<Assembly> LoadAssemblies(IEnumerable<string> paths)
{
foreach (var file in paths)

View file

@ -4,6 +4,7 @@
<OutputType>Library</OutputType>
<RootNamespace>FRESHMusicPlayer.Player</RootNamespace>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageTags>audio music-player</PackageTags>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release+Package|AnyCPU' ">
<Optimize>true</Optimize>
@ -18,13 +19,11 @@
<Compile Remove="Handlers\InternetHandler.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
<PackageReference Include="LiteDB" Version="5.0.9" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="NAudio" Version="1.10.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Composition" Version="1.4.1" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
<PackageReference Include="z440.atl.core" Version="3.7.0" />
</ItemGroup>
</Project>

View file

@ -1,144 +0,0 @@
using FRESHMusicPlayer.Utilities;
using Lite = LiteDB;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FRESHMusicPlayer.Handlers
{
public static class DatabaseHandler
{
public static readonly int DatabaseVersion = 1;
public static readonly string DatabasePath;
static DatabaseHandler()
{
DatabasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FRESHMusicPlayer");
}
#region v1
/// <summary>
/// Returns all of the tracks in the database.
/// </summary>
/// <returns>A list of file paths in the database.</returns>
public static List<string> ReadSongs()
{
if (!File.Exists(DatabasePath + "\\database.json"))
{
Directory.CreateDirectory(DatabasePath);
File.WriteAllText(DatabasePath + "\\database.json", $"{{\"Version\":{DatabaseVersion},\"Songs\":[]}}");
}
using (var file = File.OpenText(DatabasePath + "\\database.json")) // Read json file
{
var serializer = new JsonSerializer();
var database = (DatabaseFormat)serializer.Deserialize(file, typeof(DatabaseFormat));
return database.Songs;
}
}
public static void ImportSong(string filepath)
{
var database = ReadSongs();
database.Add(filepath); // Add the new song in
var format = new DatabaseFormat
{
Version = 1,
Songs = new List<string>()
};
format.Songs = database;
using (var file = File.CreateText(DatabasePath + "\\database.json"))
{
var serializer = new JsonSerializer();
serializer.Serialize(file, format);
}
}
public static void ImportSong(string[] filepath)
{
var database = ReadSongs();
database.AddRange(filepath);
var format = new DatabaseFormat
{
Version = 1,
Songs = new List<string>()
};
format.Songs = database;
using (var file = File.CreateText(DatabasePath + "\\database.json"))
{
var serializer = new JsonSerializer();
serializer.Serialize(file, format);
}
}
public static void ImportSong(List<string> filepath)
{
var database = ReadSongs();
database.AddRange(filepath);
var format = new DatabaseFormat
{
Version = 1,
Songs = new List<string>()
};
format.Songs = database;
using (var file = File.CreateText(DatabasePath + "\\database.json"))
{
var serializer = new JsonSerializer();
serializer.Serialize(file, format);
}
}
public static void ImportSong(IList<string> filepath)
{
var database = ReadSongs();
database.AddRange(filepath);
var format = new DatabaseFormat
{
Version = 1,
Songs = new List<string>()
};
format.Songs = database;
using (var file = File.CreateText(DatabasePath + "\\database.json"))
{
var serializer = new JsonSerializer();
serializer.Serialize(file, format);
}
}
public static void DeleteSong(string filepath)
{
var database = ReadSongs();
database.Remove(filepath);
var format = new DatabaseFormat
{
Version = 1,
Songs = database
};
using (var file = File.CreateText(DatabasePath + "\\database.json"))
{
var serializer = new JsonSerializer();
serializer.Serialize(file, format);
}
}
public static void ClearLibrary()
{
if (File.Exists(DatabasePath + "\\database.json"))
{
File.Delete(DatabasePath + "\\database.json");
File.WriteAllText(DatabasePath + "\\database.json", @"{""Version"":1,""Songs"":[]}");
}
}
#endregion
}
}

View file

@ -8,6 +8,13 @@ namespace FRESHMusicPlayer.Handlers
{
public class PlaybackExceptionEventArgs : EventArgs
{
public string Details { get; set; }
public Exception Exception { get; }
public string Details { get; }
public PlaybackExceptionEventArgs(Exception exception, string details)
{
Exception = exception;
Details = details;
}
}
}

View file

@ -0,0 +1,125 @@
using ATL;
using LiteDB;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FRESHMusicPlayer.Handlers
{
public class Library
{
public LiteDatabase Database { get; private set; }
public Library(LiteDatabase library)
{
Database = library;
}
public List<DatabaseTrack> Read(string filter = "Title") => Database.GetCollection<DatabaseTrack>("tracks").Query().OrderBy(filter).ToList();
public List<DatabaseTrack> ReadTracksForArtist(string artist) => Database.GetCollection<DatabaseTrack>("tracks").Query().Where(x => x.Artist == artist).OrderBy("Title").ToList();
public List<DatabaseTrack> ReadTracksForAlbum(string album) => Database.GetCollection<DatabaseTrack>("tracks").Query().Where(x => x.Album == album).OrderBy("TrackNumber").ToList();
public List<DatabaseTrack> ReadTracksForPlaylist(string playlist)
{
var x = Database.GetCollection<DatabasePlaylist>("playlists").FindOne(y => y.Name == playlist);
var z = new List<DatabaseTrack>();
foreach (string path in x.Tracks) z.Add(GetFallbackTrack(path));
return z;
}
public void AddTrackToPlaylist(string playlist, string path)
{
var x = Database.GetCollection<DatabasePlaylist>("playlists").FindOne(y => y.Name == playlist);
if (Database.GetCollection<DatabasePlaylist>("playlists").FindOne(y => y.Name == playlist) is null)
{
x = CreatePlaylist(playlist, path);
x.Tracks.Add(path);
}
else
{
x.Tracks.Add(path);
Database.GetCollection<DatabasePlaylist>("playlists").Update(x);
}
}
public void RemoveTrackFromPlaylist(string playlist, string path)
{
var x = Database.GetCollection<DatabasePlaylist>("playlists").FindOne(y => y.Name == playlist);
x.Tracks.Remove(path);
Database.GetCollection<DatabasePlaylist>("playlists").Update(x);
}
public DatabasePlaylist CreatePlaylist(string playlist, string path = null)
{
var newplaylist = new DatabasePlaylist
{
Name = playlist,
Tracks = new List<string>()
};
if (Database.GetCollection<DatabasePlaylist>("playlists").Count() == 0) newplaylist.DatabasePlaylistID = 0;
else newplaylist.DatabasePlaylistID = Database.GetCollection<DatabasePlaylist>("playlists").Query().ToList().Last().DatabasePlaylistID + 1;
if (path != null) newplaylist.Tracks.Add(path);
Database.GetCollection<DatabasePlaylist>("playlists").Insert(newplaylist);
return newplaylist;
}
public void DeletePlaylist(string playlist) => Database.GetCollection<DatabasePlaylist>("playlists").DeleteMany(x => x.Name == playlist);
public void Import(string[] tracks)
{
var stufftoinsert = new List<DatabaseTrack>();
int count = 0;
foreach (string y in tracks)
{
var track = new Track(y);
stufftoinsert.Add(new DatabaseTrack { Title = track.Title, Artist = track.Artist, Album = track.Album, Path = track.Path, TrackNumber = track.TrackNumber, Length = track.Duration });
count++;
}
Database.GetCollection<DatabaseTrack>("tracks").InsertBulk(stufftoinsert);
}
public void Import(List<string> tracks)
{
var stufftoinsert = new List<DatabaseTrack>();
foreach (string y in tracks)
{
var track = new Track(y);
stufftoinsert.Add(new DatabaseTrack { Title = track.Title, Artist = track.Artist, Album = track.Album, Path = track.Path, TrackNumber = track.TrackNumber, Length = track.Duration });
}
Database.GetCollection<DatabaseTrack>("tracks").InsertBulk(stufftoinsert);
}
public void Import(string path)
{
var track = new Track(path);
Database.GetCollection<DatabaseTrack>("tracks")
.Insert(new DatabaseTrack { Title = track.Title, Artist = track.Artist, Album = track.Album, Path = track.Path, TrackNumber = track.TrackNumber, Length = track.Duration });
}
public void Remove(string path)
{
Database.GetCollection<DatabaseTrack>("tracks").DeleteMany(x => x.Path == path);
}
public virtual void Nuke(bool nukePlaylists = true)
{
Database.GetCollection<DatabaseTrack>("tracks").DeleteAll();
if (nukePlaylists) Database.GetCollection<DatabasePlaylist>("playlists").DeleteAll();
}
public DatabaseTrack GetFallbackTrack(string path)
{
var dbTrack = Database.GetCollection<DatabaseTrack>("tracks").FindOne(x => path == x.Path);
if (dbTrack != null) return dbTrack;
else
{
var track = new Track(path);
return new DatabaseTrack { Artist = track.Artist, Title = track.Title, Album = track.Album, Length = track.Duration, Path = path, TrackNumber = track.TrackNumber };
}
}
}
public class DatabaseTrack
{
public string Path { get; set; }
public string Title { get; set; }
public string Artist { get; set; }
public string Album { get; set; }
public int TrackNumber { get; set; }
public int Length { get; set; }
}
public class DatabasePlaylist
{
public int DatabasePlaylistID { get; set; }
public string Name { get; set; }
public List<string> Tracks { get; set; }
}
}

View file

@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FRESHMusicPlayer
{
/// <summary>
/// Represents the player's queue.
/// </summary>
public class PlayQueue
{
/// <summary>
/// Gets or sets the current queue. This is settable for situations where there's no method for what you want to do.
/// Use the methods in this class for managing the queue so that events can fire and stuff doesn't break in the future.
/// </summary>
public List<string> Queue
{
get
{
if (Shuffle)
return shuffledQueue;
else return queue;
}
set
{
if (Shuffle)
{
shuffledQueue = value;
ShuffleQueue();
}
queue = value;
}
}
private bool shuffle = false;
/// <summary>
/// Gets or sets whether the queue should be shuffled.
/// </summary>
public bool Shuffle
{
get => shuffle;
set
{
shuffle = value;
if (shuffle) shuffledQueue = new List<string>(queue);
else shuffledQueue = null;
QueueChanged?.Invoke(null, EventArgs.Empty);
}
}
/// <summary>
/// Gets or sets the current repeat mode.
/// </summary>
public RepeatMode RepeatMode { get; set; } = RepeatMode.None;
/// <summary>
/// Gets or sets the index in the queue of the track that the Player is going to play *next*.
/// </summary>
public int Position { get; set; }
/// <summary>
/// Fired when the queue changes.
/// </summary>
public event EventHandler QueueChanged;
private List<string> queue = new List<string>();
private List<string> shuffledQueue;
private readonly Random rng = new Random();
/// <summary>
/// Adds a track to the queue.
/// </summary>
/// <param name="filePath">The track to add</param>
public void Add(string filePath)
{
queue.Add(filePath);
if (Shuffle)
{
shuffledQueue.Add(filePath);
ShuffleQueue();
}
QueueChanged?.Invoke(null, EventArgs.Empty);
}
/// <summary>
/// Adds multiple tracks to the queue.
/// </summary>
/// <param name="filePaths">The tracks to add.</param>
public void Add(string[] filePaths)
{
queue.AddRange(filePaths);
if (Shuffle)
{
shuffledQueue.AddRange(filePaths);
ShuffleQueue();
}
QueueChanged?.Invoke(null, EventArgs.Empty);
}
/// <summary>
/// Clears the queue.
/// </summary>
public void Clear()
{
queue.Clear();
if (Shuffle)
shuffledQueue.Clear();
Position = 0;
QueueChanged?.Invoke(null, EventArgs.Empty);
}
/// <summary>
/// Shuffles the queue. If <see cref="Shuffle"/> isn't true, this will not do anything.
/// </summary>
public void ManualShuffle()
{
if (Shuffle)
ShuffleQueue();
QueueChanged?.Invoke(null, EventArgs.Empty);
}
/// <summary>
/// Removes a track from the queue.
/// </summary>
/// <param name="index">The index of the track you want to remove.</param>
public void Remove(int index)
{
if (index <= (Position - 1)) Position--;
if (Position < 0) Position = 1;
queue.RemoveAt(index);
if (Shuffle)
shuffledQueue.RemoveAt(index);
QueueChanged?.Invoke(null, EventArgs.Empty);
}
private void ShuffleQueue()
{
var listtosort = new List<string>();
var listtoreinsert = new List<string>();
var number = 0;
foreach (var x in shuffledQueue)
{
if (Position < number) listtosort.Add(x);
else listtoreinsert.Add(x);
number++;
}
var n = listtosort.Count;
while (n > 1)
{
n--;
var k = rng.Next(n + 1);
var value = listtosort[k];
listtosort[k] = listtosort[n];
listtosort[n] = value;
}
listtoreinsert.AddRange(listtosort);
shuffledQueue = listtoreinsert;
}
}
/// <summary>
/// The way that the queue will be repeated
/// </summary>
public enum RepeatMode
{
/// <summary>
/// Do not repeat tracks in the queue
/// </summary>
None,
/// <summary>
/// Repeat the currently playing track
/// </summary>
RepeatOne,
/// <summary>
/// Repeat the entire queue
/// </summary>
RepeatAll
}
}

View file

@ -1,5 +1,4 @@
using DiscordRPC;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -12,74 +11,77 @@ namespace FRESHMusicPlayer
{
public class Player
{
/// <summary>
/// The current backend the Player is using for audio playback
/// </summary>
public IAudioBackend CurrentBackend { get; private set; }
/// <summary>
/// The current playback position./>
/// </summary>
public TimeSpan CurrentTime { get => CurrentBackend.CurrentTime; set => CurrentBackend.CurrentTime = value; }
/// <summary>
/// The total length of the current track.
/// </summary>
public TimeSpan TotalTime { get => CurrentBackend.TotalTime; }
/// <summary>
/// If true, suppresses an internal event handler. I honestly don't understand how this thing works; just make sure to keep
/// setting this to false, or things will explode.
/// </summary>
public bool AvoidNextQueue { get; set; }
public DiscordRpcClient Client { get; set; }
public float CurrentVolume { get; set; } = 1;
public string FilePath { get; set; } = string.Empty;
public bool Playing { get; set; }
private float volume = 1f;
/// <summary>
/// The current volume, from 0 to 1.
/// </summary>
public float Volume
{
get => volume;
set
{
volume = value;
if (FileLoaded)
CurrentBackend.Volume = volume;
}
}
/// <summary>
/// The current path the Player is playing. Keep in mind that this may not necessarily be a file. For example, it could be the
/// URL to a network stream.
/// </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
/// will explode.
/// </summary>
public bool FileLoaded { get; set; }
/// <summary>
/// Whether the Player is paused.
/// </summary>
public bool Paused { get; set; }
public bool RepeatOnce { get; set; } = false;
public PlayQueue Queue { get; set; } = new PlayQueue();
public bool Shuffle { get; set; } = false;
// TODO: ^^ for the two properties above, check if their setters are ever used
public List<string> Queue { get; private set; } = new List<string>();
public int QueuePosition { get; set; }
/// <summary>
/// Raised whenever a new track is being played.
/// </summary>
public event EventHandler SongChanged;
/// <summary>
/// Raised whenever the player is stopping.
/// </summary>
public event EventHandler SongStopped;
/// <summary>
/// Raised whenever an exception is thrown while the Player is loading a file.
/// </summary>
public event EventHandler<PlaybackExceptionEventArgs> SongException;
public event EventHandler QueueChanged;
#region CoreFMP
// Queue System
/// <summary>
/// Adds a track to the <see cref="Queue"/>.
/// </summary>
/// <param name="filePath">The file path to the track to add.</param>
public void AddQueue(string filePath)
{
Queue.Add(filePath);
QueueChanged?.Invoke(null, EventArgs.Empty);
}
public void AddQueue(string[] filePaths)
{
Queue.AddRange(filePaths);
QueueChanged?.Invoke(null, EventArgs.Empty);
}
public void ClearQueue()
{
Queue.Clear();
QueuePosition = 0;
QueueChanged?.Invoke(null, EventArgs.Empty);
}
public void ShuffleQueue()
{
Queue = this.ShuffleQueue(Queue);
QueueChanged?.Invoke(null, EventArgs.Empty);
}
public void RemoveQueue(int index)
{
Queue.RemoveAt(index);
if (index <= (QueuePosition - 1)) QueuePosition--;
if (QueuePosition < 0) QueuePosition = 1;
QueueChanged?.Invoke(null, EventArgs.Empty);
}
/// <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()
{
if (QueuePosition <= 1) return;
if (Shuffle) Queue = this.ShuffleQueue(Queue);
QueuePosition -= 2;
if (Queue.Position <= 1) return;
Queue.Position -= 2;
PlayMusic();
}
@ -90,13 +92,17 @@ namespace FRESHMusicPlayer
public void NextSong(bool avoidNext = false)
{
AvoidNextQueue = avoidNext;
if (RepeatOnce) QueuePosition--; // Don't advance Queue, play the same thing again
if (Shuffle) Queue = this.ShuffleQueue(Queue);
if (Queue.RepeatMode == RepeatMode.RepeatOne) Queue.Position--; // Don't advance Queue, play the same thing again
if (QueuePosition >= Queue.Count)
if (Queue.Position >= Queue.Queue.Count)
{
if (Queue.RepeatMode == RepeatMode.RepeatAll) // Go back to the first track and play it again
{
Queue.Position = 0;
PlayMusic();
return;
}
Queue.Clear();
QueuePosition = 0;
StopMusic();
return;
}
@ -121,30 +127,38 @@ namespace FRESHMusicPlayer
CurrentBackend.CurrentTime = TimeSpan.FromSeconds(seconds);
}
/// <summary>
/// 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)
{
Queue.Add(path);
PlayMusic();
}
/// <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)
{
if (!repeat && Queue.Count != 0)
FilePath = Queue[QueuePosition];
QueuePosition++;
QueueChanged?.Invoke(null, EventArgs.Empty);
if (!repeat && Queue.Queue.Count != 0)
FilePath = Queue.Queue[Queue.Position];
Queue.Position++;
void PMusic()
{
CurrentBackend = AudioBackendFactory.CreateBackend(FilePath);
CurrentBackend.Play();
CurrentBackend.Volume = CurrentVolume;
CurrentBackend.Volume = Volume;
CurrentBackend.OnPlaybackStopped += OnPlaybackStopped;
Playing = true;
FileLoaded = true;
}
try
{
if (Playing != true)
if (FileLoaded != true)
{
PMusic();
}
@ -158,34 +172,34 @@ namespace FRESHMusicPlayer
SongChanged?.Invoke(null,
EventArgs.Empty); // Now that playback has started without any issues, fire the song changed event.
}
catch (FileNotFoundException)
{
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 (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 {Details = $"{e.Message}\n{e.StackTrace}"};
var args = new PlaybackExceptionEventArgs(e, $"{e.Message}\n{e.StackTrace}");
SongException?.Invoke(null, args);
}
}
@ -195,12 +209,12 @@ namespace FRESHMusicPlayer
/// </summary>
public void StopMusic()
{
if (!Playing) return;
if (!FileLoaded) return;
CurrentBackend.Dispose();
CurrentBackend = null;
Playing = false;
FileLoaded = false;
Paused = false;
SongStopped?.Invoke(null, EventArgs.Empty);
}
@ -210,72 +224,57 @@ namespace FRESHMusicPlayer
/// </summary>
public void PauseMusic()
{
if (!Paused) CurrentBackend?.Pause();
if (!Paused)
CurrentBackend?.Pause();
Paused = true;
} // Pauses the music without completely disposing it
}
/// <summary>
/// Resumes playback.
/// </summary>
public void ResumeMusic()
{
if (Paused) CurrentBackend?.Play();
//playing = true;
if (Paused)
CurrentBackend?.Play();
Paused = false;
} // Resumes music that has been paused
/// <summary>
/// Updates the volume of the player during playback to the value of <see cref="CurrentVolume"/>.
/// Even if you don't call this, the volume of the player will update whenever the next track plays.
/// </summary>
public void UpdateSettings()
{
CurrentBackend.Volume = CurrentVolume;
}
// Other Logic Stuff
/// <summary>
/// Returns a formatted string of the current playback position.
/// </summary>
/// <returns></returns>
public string SongPositionString() => $"{CurrentBackend.CurrentTime:c} / {CurrentBackend.TotalTime:c}";
#endregion
// Integration
#region DiscordRPC
/// <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);
//#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();
}
// 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 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();
//public void DisposeRPC() => Client?.Dispose();
#endregion
//#endregion
}
}

View file

@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Squid Grill")]
[assembly: AssemblyProduct("FRESHMusicPlayer.Player")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@ -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("2.9.0.0")]
[assembly: AssemblyFileVersion("2.9.0.0")]
[assembly: AssemblyVersion("3.0.0.0")]
[assembly: AssemblyFileVersion("3.0.0.0")]

View file

@ -10,30 +10,6 @@ namespace FRESHMusicPlayer.Utilities
{
private static readonly Random rng = new Random();
public static List<string> ShuffleQueue(this Player player, List<string> list)
{
var listtosort = new List<string>();
var listtoreinsert = new List<string>();
var number = 0;
foreach (var x in list)
{
if (player.QueuePosition < number) listtosort.Add(x);
else listtoreinsert.Add(x);
number++;
}
var n = listtosort.Count;
while (n > 1)
{
n--;
var k = rng.Next(n + 1);
var value = listtosort[k];
listtosort[k] = listtosort[n];
listtosort[n] = value;
}
foreach (var x in listtosort) listtoreinsert.Add(x);
return listtoreinsert;
}
public static string TruncateBytes(string str, int bytes)
{
if (Encoding.UTF8.GetByteCount(str) <= bytes) return str;

View file

@ -8,19 +8,4 @@ namespace FRESHMusicPlayer.Utilities
public int Version { get; set; }
public List<string> Songs { get; set; }
}
public class DatabaseTrack
{
public string Path { get; set; }
public string Title { get; set; }
public string Artist { get; set; }
public string Album { get; set; }
public int TrackNumber { get; set; }
public int Length { get; set; }
}
public class DatabasePlaylist
{
public int DatabasePlaylistID { get; set; }
public string Name { get; set; }
public List<string> Tracks { get; set; }
}
}

View file

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Composition;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using CDLib;
using FRESHMusicPlayer.Backends;
namespace FmpCdLibBackend
{
[Export(typeof(IAudioBackend))]
public class FmpCdLibBackend : IAudioBackend
{
private IAudioCDPlayer player;
public TimeSpan CurrentTime { get => player.CurrentPosition; set => player.Seek(value); }
public TimeSpan TotalTime { get; private set; }
public float Volume { get => (float)player.Volume; set => player.Volume = value; }
public event EventHandler<EventArgs> OnPlaybackStopped;
public FmpCdLibBackend()
{
player = AudioCDPlayer.GetPlayer();
player.FinishedPlayingTrack += Player_FinishedPlayingTrack;
}
//static FmpCdLibBackend()
//{
// Assembly.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FRESHMusicPlayer", "Backends", "CDLib.winmd"));
// SetDllDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FRESHMusicPlayer", "Backends"));
//}
private void Player_FinishedPlayingTrack()
{
OnPlaybackStopped.Invoke(null, EventArgs.Empty);
}
public void Dispose()
{
player.Pause();
//player.Dispose();
}
public void LoadSong(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));
var drives = player.GetDrives();
foreach (var drive in drives)
{
if (drive.DriveLetter == driveLetter)
{
var trackToPlay = drive.InsertedMedia.Tracks[trackNumber - 1];
TotalTime = trackToPlay.Duration;
player.PlayTrack(trackToPlay);
return;
}
}
}
public void Pause()
{
player.Pause();
}
public void Play()
{
player.Resume();
}
[DllImport("kernel32")]
private static extern void SetDllDirectory(string pathName);
}
}

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{31D77A1D-F161-42E8-826E-3E27E6566B28}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FmpCdLibBackend</RootNamespace>
<AssemblyName>FmpCdLibBackend</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="CDLib, Version=255.255.255.255, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\CDLib\Release\CDLib.winmd</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="FmpCdLibBackend.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FRESHMusicPlayer.Player\FRESHMusicPlayer.Core.csproj">
<Project>{ffe8eafd-7faa-4503-9364-2643821a2bc8}</Project>
<Name>FRESHMusicPlayer.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Composition">
<Version>1.4.1</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("FmpCdLibBackend")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FmpCdLibBackend")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("31d77a1d-f161-42e8-826e-3e27e6566b28")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using FRESHMusicPlayer;
using FRESHMusicPlayer.Handlers;
@ -17,9 +18,20 @@ namespace WinformsTest
player.SongChanged += Player_songChanged;
player.SongStopped += Player_songStopped;
player.SongException += Player_songException;
player.Queue.QueueChanged += Queue_QueueChanged;
library = DatabaseHandler.ReadSongs();
}
private void Queue_QueueChanged(object sender, EventArgs e)
{
var builder = new StringBuilder();
foreach (var track in player.Queue.Queue)
{
builder.AppendLine(track);
}
MessageBox.Show(builder.ToString());
}
private void Player_songException(object sender, PlaybackExceptionEventArgs e)
{
MessageBox.Show("something did a fucky wucky");
@ -37,22 +49,33 @@ namespace WinformsTest
private void button1_Click(object sender, EventArgs e)
{
if (player.Paused) player.ResumeMusic();
else player.PauseMusic();
var builder = new StringBuilder();
foreach (var track in player.Queue.Queue)
{
builder.AppendLine(track);
}
MessageBox.Show(builder.ToString());
player.Queue.ManualShuffle();
}
private void button2_Click(object sender, EventArgs e)
{
player.Queue.Shuffle = !player.Queue.Shuffle;
MessageBox.Show(player.Queue.Shuffle.ToString());
}
private void button3_Click(object sender, EventArgs e)
{
var openFileDialog1 = new OpenFileDialog();
if (openFileDialog1.ShowDialog() != DialogResult.OK) return;
player.AddQueue(openFileDialog1.FileName);
player.PlayMusic();
player.CurrentVolume = 0.2f;
player.UpdateSettings();
var list = new List<string>();
for (var i = 1; i < 100; i++)
list.Add(i.ToString());
player.Queue.Add(list.ToArray());
//var openFileDialog1 = new OpenFileDialog();
//if (openFileDialog1.ShowDialog() != DialogResult.OK) return;
//player.Queue.Add(openFileDialog1.FileName);
//player.PlayMusic();
//player.CurrentVolume = 0.2f;
//player.UpdateSettings();
}
}
}

View file

@ -10,7 +10,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.11" />
</ItemGroup>
</Project>

View file

@ -1,21 +1,25 @@
banner goes here eventually
# FRESHMusicPlayer-Core
Music Player component for .NET apps - designed to be used for the FRESHMusicPlayer project but can be used on anything :)
audio library abstraction library - designed to be used for the FRESHMusicPlayer project but can be used on anything :)
- [**Support/Discussion Discord Server**](https://discord.gg/mFGFT8K)
- [**Documentation (WIP)**]()
## Features
- Abstracts music playback into a simple API
- Provides a library that's shared between all FMP Core based apps
- Has built in support for integrations with services like Discord
- todo: add more stuff here
## Usage
```
using FRESHMusicPlayer;
Player player = new Player();
var player = new Player();
string path = "Can be a file path, or a URL to a network stream";
player.AddQueue(path); // Everything in FMP runs on a queue
player.PlayMusic(); // Play through the queue
player.PlayMusic(path); // Clear the queue, add the track to the queue, and play
// or
player.Queue.Add(path);
player.PlayMusic(); // Plays through the queue
```
[**Documentation (WIP)**]()
### Platforms
**Windows** - NAudio is required for audio playback
**Other platforms** - You'll need to include the [VLC audio plugin](https://github.com/DeclanHoare/FmpVlcBackend) in the output directory of your app for audio playback to work. The user will also need to have a global installation of VLC available. Hoping to replace this with something lighterweight in the future.
## Projects that use FMP Core
- [**FRESHMusicPlayer**](https://github.com/royce551/freshmusicplayer)
- [**FRESHMusicPlayer-WinForms**](https://github.com/royce551/freshmusicplayer-wpfui)