diff --git a/Harmonia.WinUI/App.xaml b/Harmonia.WinUI/App.xaml
new file mode 100644
index 0000000..7eeb327
--- /dev/null
+++ b/Harmonia.WinUI/App.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Harmonia.WinUI/App.xaml.cs b/Harmonia.WinUI/App.xaml.cs
new file mode 100644
index 0000000..d6b7a73
--- /dev/null
+++ b/Harmonia.WinUI/App.xaml.cs
@@ -0,0 +1,50 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+using Microsoft.UI.Xaml.Shapes;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace Harmonia.WinUI
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : Application
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// Invoked when the application is launched.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
+ {
+ m_window = new MainWindow();
+ m_window.Activate();
+ }
+
+ private Window? m_window;
+ }
+}
diff --git a/Harmonia.WinUI/Assets/LockScreenLogo.scale-200.png b/Harmonia.WinUI/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..7440f0d
Binary files /dev/null and b/Harmonia.WinUI/Assets/LockScreenLogo.scale-200.png differ
diff --git a/Harmonia.WinUI/Assets/SplashScreen.scale-200.png b/Harmonia.WinUI/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..32f486a
Binary files /dev/null and b/Harmonia.WinUI/Assets/SplashScreen.scale-200.png differ
diff --git a/Harmonia.WinUI/Assets/Square150x150Logo.scale-200.png b/Harmonia.WinUI/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..53ee377
Binary files /dev/null and b/Harmonia.WinUI/Assets/Square150x150Logo.scale-200.png differ
diff --git a/Harmonia.WinUI/Assets/Square44x44Logo.scale-200.png b/Harmonia.WinUI/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..f713bba
Binary files /dev/null and b/Harmonia.WinUI/Assets/Square44x44Logo.scale-200.png differ
diff --git a/Harmonia.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Harmonia.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..dc9f5be
Binary files /dev/null and b/Harmonia.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Harmonia.WinUI/Assets/StoreLogo.png b/Harmonia.WinUI/Assets/StoreLogo.png
new file mode 100644
index 0000000..a4586f2
Binary files /dev/null and b/Harmonia.WinUI/Assets/StoreLogo.png differ
diff --git a/Harmonia.WinUI/Assets/Wide310x150Logo.scale-200.png b/Harmonia.WinUI/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..8b4a5d0
Binary files /dev/null and b/Harmonia.WinUI/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs b/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs
new file mode 100644
index 0000000..c92b011
--- /dev/null
+++ b/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs
@@ -0,0 +1,92 @@
+using Harmonia.Core.Caching;
+using Harmonia.Core.Imaging;
+using Harmonia.Core.Models;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.UI.Xaml.Media.Imaging;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Harmonia.WinUI.Caching;
+
+public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : MemoryCache, IAudioBitmapImageCache
+{
+ protected virtual int MaxImageWidthOrHeight => 1000;
+
+ protected override MemoryCacheOptions Options => new()
+ {
+ SizeLimit = 40,
+ CompactionPercentage = 0.2,
+ };
+
+ protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600);
+
+ protected override int MaxConcurrentRequests => 8;
+
+ protected override object? GetKey(Song key)
+ {
+ if (string.IsNullOrWhiteSpace(key.ImageHash) == false)
+ {
+ return key.ImageHash;
+ }
+ else if (string.IsNullOrWhiteSpace(key.ImageName) == false)
+ {
+ return key.ImageName;
+ }
+
+ return null;
+ }
+
+ protected override async ValueTask FetchAsync(Song key, CancellationToken cancellationToken)
+ {
+ SongPictureInfo? songPictureInfo = await audioImageExtractor.ExtractImageAsync(key.FileName, cancellationToken);
+
+ if (songPictureInfo == null)
+ return null;
+
+ using MemoryStream stream = new(songPictureInfo.Data);
+
+ BitmapImage bitmapImage = new();
+
+ await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream());
+
+ bitmapImage.DecodePixelWidth = GetDecodePixelWidth(bitmapImage);
+ bitmapImage.DecodePixelHeight = GetDecodePixelHeight(bitmapImage);
+
+ return bitmapImage;
+ }
+
+ private int GetDecodePixelWidth(BitmapImage bitmapImage)
+ {
+ int originalImageWidth = bitmapImage.PixelWidth;
+ int orignalImageHeight = bitmapImage.PixelHeight;
+
+ if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight)
+ return 0;
+
+ if (orignalImageHeight > originalImageWidth)
+ return 0;
+
+ return MaxImageWidthOrHeight;
+ }
+
+ private int GetDecodePixelHeight(BitmapImage bitmapImage)
+ {
+ int originalImageWidth = bitmapImage.PixelWidth;
+ int orignalImageHeight = bitmapImage.PixelHeight;
+
+ if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight)
+ return 0;
+
+ if (originalImageWidth > orignalImageHeight)
+ return 0;
+
+ return MaxImageWidthOrHeight;
+ }
+
+ protected override long GetEntrySize(BitmapImage entry)
+ {
+ return entry.PixelWidth * entry.PixelHeight;
+ }
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Caching/IAudioBitmapImageCache.cs b/Harmonia.WinUI/Caching/IAudioBitmapImageCache.cs
new file mode 100644
index 0000000..3f67447
--- /dev/null
+++ b/Harmonia.WinUI/Caching/IAudioBitmapImageCache.cs
@@ -0,0 +1,10 @@
+using Harmonia.Core.Caching;
+using Harmonia.Core.Models;
+using Microsoft.UI.Xaml.Media.Imaging;
+
+namespace Harmonia.WinUI.Caching;
+
+public interface IAudioBitmapImageCache : ICache
+{
+
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs b/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs
new file mode 100644
index 0000000..f211f34
--- /dev/null
+++ b/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs
@@ -0,0 +1,26 @@
+using Microsoft.UI.Xaml.Data;
+using System;
+using System.Collections.Generic;
+
+namespace Harmonia.WinUI.Converters;
+
+public partial class ArtistsToStringConverter : IValueConverter
+{
+ public ArtistsToStringConverter()
+ {
+
+ }
+
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is not string[] artists)
+ return string.Empty;
+
+ return string.Join(" / ", artists);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Converters/NullVisibilityConverter.cs b/Harmonia.WinUI/Converters/NullVisibilityConverter.cs
new file mode 100644
index 0000000..9b76c2e
--- /dev/null
+++ b/Harmonia.WinUI/Converters/NullVisibilityConverter.cs
@@ -0,0 +1,27 @@
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml;
+using System;
+using System.Collections;
+
+namespace Harmonia.WinUI.Converters;
+
+public sealed partial class NullVisibilityConverter : IValueConverter
+{
+ public NullVisibilityConverter()
+ {
+
+ }
+
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is not IList list)
+ return Visibility.Collapsed;
+
+ return list.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Converters/RepeatStateConverter.cs b/Harmonia.WinUI/Converters/RepeatStateConverter.cs
new file mode 100644
index 0000000..9f94d83
--- /dev/null
+++ b/Harmonia.WinUI/Converters/RepeatStateConverter.cs
@@ -0,0 +1,26 @@
+using Harmonia.Core.Player;
+using Microsoft.UI.Xaml.Data;
+using System;
+
+namespace Harmonia.WinUI.Converters;
+
+public sealed partial class RepeatStateConverter : IValueConverter
+{
+ public RepeatStateConverter()
+ {
+
+ }
+
+ public object? Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is not RepeatState repeatState)
+ return null;
+
+ return repeatState.ToString();
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Converters/SecondsToStringConverter.cs b/Harmonia.WinUI/Converters/SecondsToStringConverter.cs
new file mode 100644
index 0000000..5796340
--- /dev/null
+++ b/Harmonia.WinUI/Converters/SecondsToStringConverter.cs
@@ -0,0 +1,30 @@
+using Microsoft.UI.Xaml.Data;
+using System;
+
+namespace Harmonia.WinUI.Converters;
+
+public sealed partial class SecondsToStringConverter : IValueConverter
+{
+ public SecondsToStringConverter()
+ {
+
+ }
+
+ public object? Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is not double doubleValue)
+ return null;
+
+ TimeSpan timeSpan = TimeSpan.FromSeconds(doubleValue);
+
+ if (timeSpan.Hours >= 1)
+ return timeSpan.ToString(@"%h\:mm\:ss");
+
+ return timeSpan.ToString(@"%m\:ss");
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ return TimeSpan.Parse((string)value);
+ }
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Converters/SongTitleConverter.cs b/Harmonia.WinUI/Converters/SongTitleConverter.cs
new file mode 100644
index 0000000..0c807d2
--- /dev/null
+++ b/Harmonia.WinUI/Converters/SongTitleConverter.cs
@@ -0,0 +1,26 @@
+using Harmonia.Core.Models;
+using Microsoft.UI.Xaml.Data;
+using System;
+
+namespace Harmonia.WinUI.Converters;
+
+public sealed partial class SongTitleConverter : IValueConverter
+{
+ public SongTitleConverter()
+ {
+
+ }
+
+ public object? Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is not Song song)
+ return null;
+
+ return string.IsNullOrWhiteSpace(song.Title) ? song.ShortFileName : song.Title;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Converters/VolumeStateToIconConverter.cs b/Harmonia.WinUI/Converters/VolumeStateToIconConverter.cs
new file mode 100644
index 0000000..cbcce10
--- /dev/null
+++ b/Harmonia.WinUI/Converters/VolumeStateToIconConverter.cs
@@ -0,0 +1,26 @@
+using System;
+using Microsoft.UI.Xaml.Data;
+using Harmonia.WinUI.Models;
+
+namespace Harmonia.WinUI.Converters;
+
+public sealed partial class VolumeStateConverter : IValueConverter
+{
+ public VolumeStateConverter()
+ {
+
+ }
+
+ public object? Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is not VolumeState volumeState)
+ return null;
+
+ return volumeState.ToString();
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Harmonia.WinUI.csproj b/Harmonia.WinUI/Harmonia.WinUI.csproj
new file mode 100644
index 0000000..89e81f2
--- /dev/null
+++ b/Harmonia.WinUI/Harmonia.WinUI.csproj
@@ -0,0 +1,64 @@
+
+
+ WinExe
+ net9.0-windows10.0.19041.0
+ 10.0.17763.0
+ Harmonia.WinUI
+ app.manifest
+ x86;x64;ARM64
+ win-x86;win-x64;win-arm64
+ win-$(Platform).pubxml
+ true
+ true
+ None
+ true
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+ False
+ True
+ False
+ True
+
+
\ No newline at end of file
diff --git a/Harmonia.WinUI/MainWindow.xaml b/Harmonia.WinUI/MainWindow.xaml
new file mode 100644
index 0000000..3207319
--- /dev/null
+++ b/Harmonia.WinUI/MainWindow.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/Harmonia.WinUI/MainWindow.xaml.cs b/Harmonia.WinUI/MainWindow.xaml.cs
new file mode 100644
index 0000000..a0ddcf0
--- /dev/null
+++ b/Harmonia.WinUI/MainWindow.xaml.cs
@@ -0,0 +1,36 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace Harmonia.WinUI
+{
+ ///
+ /// An empty window that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ this.InitializeComponent();
+ }
+
+ private void myButton_Click(object sender, RoutedEventArgs e)
+ {
+ myButton.Content = "Clicked";
+ }
+ }
+}
diff --git a/Harmonia.WinUI/Models/VolumeState.cs b/Harmonia.WinUI/Models/VolumeState.cs
new file mode 100644
index 0000000..a30102a
--- /dev/null
+++ b/Harmonia.WinUI/Models/VolumeState.cs
@@ -0,0 +1,10 @@
+namespace Harmonia.WinUI.Models;
+
+public enum VolumeState
+{
+ Muted,
+ Off,
+ Low,
+ Medium,
+ High
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/Package.appxmanifest b/Harmonia.WinUI/Package.appxmanifest
new file mode 100644
index 0000000..e82a799
--- /dev/null
+++ b/Harmonia.WinUI/Package.appxmanifest
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+ Harmonia.WinUI
+ Brian
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Harmonia.WinUI/Properties/launchSettings.json b/Harmonia.WinUI/Properties/launchSettings.json
new file mode 100644
index 0000000..7c79af8
--- /dev/null
+++ b/Harmonia.WinUI/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "Harmonia.WinUI (Package)": {
+ "commandName": "MsixPackage"
+ },
+ "Harmonia.WinUI (Unpackaged)": {
+ "commandName": "Project"
+ }
+ }
+}
\ No newline at end of file
diff --git a/Harmonia.WinUI/app.manifest b/Harmonia.WinUI/app.manifest
new file mode 100644
index 0000000..87a6138
--- /dev/null
+++ b/Harmonia.WinUI/app.manifest
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PerMonitorV2
+
+
+
\ No newline at end of file
diff --git a/Harmonia.sln b/Harmonia.sln
index 1042818..295c649 100644
--- a/Harmonia.sln
+++ b/Harmonia.sln
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harmonia.UI", "Harmonia.UI\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harmonia.UI.Desktop", "Harmonia.UI.Desktop\Harmonia.UI.Desktop.csproj", "{61B86E6A-A7E9-4946-E488-40A7E12ED4FC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harmonia.WinUI", "Harmonia.WinUI\Harmonia.WinUI.csproj", "{5972FBF1-E81A-4A80-9153-13F45FA0E2AA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,12 @@ Global
{61B86E6A-A7E9-4946-E488-40A7E12ED4FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61B86E6A-A7E9-4946-E488-40A7E12ED4FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61B86E6A-A7E9-4946-E488-40A7E12ED4FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Debug|Any CPU.Build.0 = Debug|x64
+ {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Debug|Any CPU.Deploy.0 = Debug|x64
+ {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Release|Any CPU.ActiveCfg = Release|x64
+ {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Release|Any CPU.Build.0 = Release|x64
+ {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Release|Any CPU.Deploy.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE