diff --git a/.github/main.yml b/.github/main.yml
new file mode 100644
index 0000000..a72eb55
--- /dev/null
+++ b/.github/main.yml
@@ -0,0 +1,36 @@
+name: CI/CD
+
+on:
+ push:
+ branches:
+ - main
+ - '*'
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Install tools
+ run: dotnet tool install --global vpk
+
+ - name: Dotnet publish
+ run: dotnet publish src/Plpext/Plpext.UI/Plpext.UI.csproj -c Release -r win-x64 -o publish
+
+ - name: Vpk pack
+ run: vpk pack --mainExe=Plpext.exe --packId=Plpext --packVersion=1.0.0 --packDir=publish -o build/win
+
+ - name: Upload artifact
+ uses: softprops/action-gh-release@v2
+ with:
+ files: build/win/Plpext-win-Setup.exe
diff --git a/.gitignore b/.gitignore
index 4de168d..ad64cdb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@ artifacts/
*.suo
*.userprefs
*DS_Store
-*.sln.ide
\ No newline at end of file
+*.sln.ide
+/build/win
diff --git a/src/Plpext.Core.Windows/Plpext.Core.Windows.csproj b/src/Plpext.Core.Windows/Plpext.Core.Windows.csproj
index 0af2c11..3fbda68 100644
--- a/src/Plpext.Core.Windows/Plpext.Core.Windows.csproj
+++ b/src/Plpext.Core.Windows/Plpext.Core.Windows.csproj
@@ -4,14 +4,14 @@
net8.0
enable
enable
- x86;x64
+ x86;x64;AnyCPU
-
+
Always
OpenAL32.dll
diff --git a/src/Plpext.Core/AudioConverter/MP3AudioConverter.cs b/src/Plpext.Core/AudioConverter/MP3AudioConverter.cs
index 7c9dfb5..317b27e 100644
--- a/src/Plpext.Core/AudioConverter/MP3AudioConverter.cs
+++ b/src/Plpext.Core/AudioConverter/MP3AudioConverter.cs
@@ -61,7 +61,7 @@ namespace Plpext.Core.AudioConverter
var audioDuration = (double)(pcmData.Length / 2) / mp3Stream.Frequency;
- return new AudioFile() { Name = baseFile.Name, Data = pcmData, Duration = TimeSpan.FromSeconds(audioDuration), Format = AudioFormat.Mono16, Frequency = mp3Stream.Frequency };
+ return new AudioFile() { Name = baseFile.Name, MP3Data = file, Data = pcmData, Duration = TimeSpan.FromSeconds(audioDuration), Format = AudioFormat.Mono16, Frequency = mp3Stream.Frequency };
}
private static byte[] ResampleToMono(Span data)
diff --git a/src/Plpext.Core/AudioPlayer/AudioPlayer.cs b/src/Plpext.Core/AudioPlayer/AudioPlayer.cs
index 81d661c..71dfb14 100644
--- a/src/Plpext.Core/AudioPlayer/AudioPlayer.cs
+++ b/src/Plpext.Core/AudioPlayer/AudioPlayer.cs
@@ -26,23 +26,24 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
public PlaybackState State { get; private set; } = PlaybackState.Stopped;
- public async Task InitAudioPlayerAsync(AudioFile input, CancellationToken cancellationToken)
+ public async Task InitAudioPlayerAsync(AudioFile input, bool autoStart, CancellationToken cancellationToken)
{
_audioFile = input;
-
_playbackCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
try
{
int bufferId = AL.GenBuffer();
int sourceId = AL.GenSource();
-
+
_currentBufferId = bufferId;
_currentSourceId = sourceId;
AL.BufferData(bufferId, ALFormat.Mono16, input.Data.Span, input.Frequency);
AL.Source(sourceId, ALSourcei.Buffer, bufferId);
- return await Start();
+ if(autoStart)
+ return await Start();
+ return true;
}
catch (Exception ex)
{
diff --git a/src/Plpext.Core/Interfaces/IAudioPlayer.cs b/src/Plpext.Core/Interfaces/IAudioPlayer.cs
index 9504d90..9ae75da 100644
--- a/src/Plpext.Core/Interfaces/IAudioPlayer.cs
+++ b/src/Plpext.Core/Interfaces/IAudioPlayer.cs
@@ -9,7 +9,7 @@ namespace Plpext.Core.Interfaces;
public interface IAudioPlayer
{
- Task InitAudioPlayerAsync(AudioFile input, CancellationToken cancellationToken);
+ Task InitAudioPlayerAsync(AudioFile input, bool autoStart, CancellationToken cancellationToken);
void Resume();
void Pause();
void Stop();
diff --git a/src/Plpext.Core/Models/AudioFile.cs b/src/Plpext.Core/Models/AudioFile.cs
index 6ffcb59..a979428 100644
--- a/src/Plpext.Core/Models/AudioFile.cs
+++ b/src/Plpext.Core/Models/AudioFile.cs
@@ -9,6 +9,7 @@ namespace Plpext.Core.Models
public record AudioFile
{
public required string Name { get; init; }
+ public ReadOnlyMemory MP3Data { get; init; }
public ReadOnlyMemory Data { get; init; }
public TimeSpan Duration { get; init; }
public AudioFormat Format { get; init; }
diff --git a/src/Plpext/Plpext.UI/App.axaml b/src/Plpext/Plpext.UI/App.axaml
new file mode 100644
index 0000000..74f1108
--- /dev/null
+++ b/src/Plpext/Plpext.UI/App.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Plpext/Plpext.UI/App.axaml.cs b/src/Plpext/Plpext.UI/App.axaml.cs
new file mode 100644
index 0000000..96c8bba
--- /dev/null
+++ b/src/Plpext/Plpext.UI/App.axaml.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Data.Core;
+using Avalonia.Data.Core.Plugins;
+using Avalonia.Markup.Xaml;
+using Microsoft.Extensions.DependencyInjection;
+using Plpext.Core.AudioPlayer;
+using Plpext.UI.DependencyInjection;
+using Plpext.UI.ViewModels;
+using Plpext.UI.Views;
+
+namespace Plpext.UI
+{
+ public class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ DisableAvaloniaDataAnnotationValidation();
+ Container.Services.GetRequiredService().Initialize();
+ desktop.MainWindow = Container.Services.GetRequiredService();
+ desktop.MainWindow.DataContext = Container.Services.GetRequiredService();
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+
+ private void DisableAvaloniaDataAnnotationValidation()
+ {
+ var dataValidationPluginsToRemove =
+ BindingPlugins.DataValidators.OfType().ToArray();
+
+ foreach (var plugin in dataValidationPluginsToRemove)
+ {
+ BindingPlugins.DataValidators.Remove(plugin);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/plpext.png b/src/Plpext/Plpext.UI/Assets/plpext.png
similarity index 100%
rename from docs/plpext.png
rename to src/Plpext/Plpext.UI/Assets/plpext.png
diff --git a/src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml b/src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml
new file mode 100644
index 0000000..d9b76f0
--- /dev/null
+++ b/src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml.cs b/src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml.cs
new file mode 100644
index 0000000..e03ce1e
--- /dev/null
+++ b/src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml.cs
@@ -0,0 +1,107 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using System.Windows.Input;
+using Avalonia.Data;
+using Plpext.Core.Models;
+
+namespace Plpext.UI;
+
+public class AudioPlayerControl : TemplatedControl
+{
+ public static readonly StyledProperty PlaybackStateProperty =
+ AvaloniaProperty.Register(nameof(PlaybackState), defaultValue: PlaybackState.Unknown, inherits: false, defaultBindingMode: BindingMode.OneWay);
+
+ public PlaybackState PlaybackState
+ {
+ get {return GetValue(PlaybackStateProperty);}
+ set {SetValue(PlaybackStateProperty, value);}
+ }
+
+ public static readonly StyledProperty IsPlayingProperty =
+ AvaloniaProperty.Register(nameof(IsPlaying), false, false, Avalonia.Data.BindingMode.TwoWay);
+
+ public bool IsPlaying
+ {
+ get { return GetValue(IsPlayingProperty); }
+ set { SetValue(IsPlayingProperty, value); }
+ }
+
+ public static readonly StyledProperty CurrentDurationProperty = AvaloniaProperty.Register
+ (
+ name: nameof(CurrentDuration),
+ defaultValue: "0:55",
+ inherits: false,
+ defaultBindingMode: Avalonia.Data.BindingMode.TwoWay,
+ validate: (x) => x != "0"
+ );
+
+ public string CurrentDuration
+ {
+ get { return GetValue(CurrentDurationProperty); }
+ set { SetValue(CurrentDurationProperty, value); }
+ }
+
+ public static readonly StyledProperty TotalDurationProperty = AvaloniaProperty.Register
+ (
+ name: nameof(TotalDuration),
+ defaultValue: "1:07",
+ inherits: false,
+ defaultBindingMode: Avalonia.Data.BindingMode.TwoWay,
+ validate: (x) => x != "0"
+ );
+
+ public string TotalDuration
+ {
+ get { return GetValue(TotalDurationProperty); }
+ set { SetValue(TotalDurationProperty, value); }
+ }
+
+ public static readonly StyledProperty ProgressProperty = AvaloniaProperty.Register
+ (
+ name: nameof(Progress),
+ defaultValue: 0,
+ inherits: false,
+ defaultBindingMode: Avalonia.Data.BindingMode.TwoWay,
+ validate: (x) => x >= 0
+ );
+
+ public double Progress
+ {
+ get { return GetValue(ProgressProperty); }
+ set { SetValue(ProgressProperty, value); }
+ }
+
+ public static readonly StyledProperty PlayCommandProperty =
+ AvaloniaProperty.Register