This commit is contained in:
parent
2a8166a0c5
commit
d881ed69e2
33 changed files with 580 additions and 640 deletions
|
@ -2,85 +2,80 @@
|
||||||
using Plpext.Core.Interfaces;
|
using Plpext.Core.Interfaces;
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
|
|
||||||
namespace Plpext.Core.AudioConverter
|
namespace Plpext.Core.AudioConverter;
|
||||||
{
|
|
||||||
public class MP3AudioConverter : IAudioConverter
|
|
||||||
{
|
|
||||||
private readonly IMP3Parser _parser;
|
|
||||||
|
|
||||||
public MP3AudioConverter(IMP3Parser parser)
|
public class MP3AudioConverter : IAudioConverter
|
||||||
|
{
|
||||||
|
private readonly IMP3Parser _parser;
|
||||||
|
|
||||||
|
public MP3AudioConverter(IMP3Parser parser)
|
||||||
|
{
|
||||||
|
_parser = parser;
|
||||||
|
}
|
||||||
|
public async Task<AudioFile> ConvertAudioAsync(ReadOnlyMemory<byte> mp3Input, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var baseFile = await _parser.ParseIntoMP3(mp3Input, cancellationToken);
|
||||||
|
byte[] pcmData = null!;
|
||||||
|
await using var mp3Stream = new MP3Stream(new MemoryStream(baseFile.Data.ToArray()));
|
||||||
|
await using var pcmStream = new MemoryStream();
|
||||||
|
var buffer = new byte[4096];
|
||||||
|
int bytesReturned = 1;
|
||||||
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_parser = parser;
|
while (bytesReturned > 0)
|
||||||
}
|
|
||||||
public async Task<AudioFile> ConvertAudioAsync(ReadOnlyMemory<byte> file, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var baseFile = await _parser.ParseIntoMP3(file, cancellationToken);
|
|
||||||
byte[] pcmData = null!;
|
|
||||||
using var mp3Stream = new MP3Stream(new MemoryStream(baseFile.Data.ToArray()));
|
|
||||||
using var pcmStream = new MemoryStream();
|
|
||||||
var buffer = new byte[4096];
|
|
||||||
int bytesReturned = 1;
|
|
||||||
int totalBytesRead = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
while (bytesReturned > 0)
|
try
|
||||||
{
|
{
|
||||||
try
|
bytesReturned = await mp3Stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
|
||||||
{
|
|
||||||
bytesReturned = await mp3Stream.ReadAsync(buffer, 0, buffer.Length);
|
|
||||||
}
|
|
||||||
catch (IndexOutOfRangeException ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{ex.Message}");
|
|
||||||
Console.WriteLine($"File reached a corrupted/non-compliant portion. Total bytes read: {totalBytesRead} | File: {baseFile.Name}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (NullReferenceException ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{ex.Message}");
|
|
||||||
Console.WriteLine($"File reached a corrupted/non-compliant portion. Total bytes read: {totalBytesRead} | File: {baseFile.Name}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
totalBytesRead += bytesReturned;
|
|
||||||
await pcmStream.WriteAsync(buffer, 0, bytesReturned);
|
|
||||||
}
|
}
|
||||||
}
|
catch (IndexOutOfRangeException)
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Exception details:");
|
|
||||||
Console.WriteLine($"Message: {ex.Message}");
|
|
||||||
Console.WriteLine($"StackTrace: {ex.StackTrace}");
|
|
||||||
if (ex.InnerException != null)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine($"InnerException: {ex.InnerException.Message}");
|
//File reached a corrupted/non-compliant portion.
|
||||||
Console.WriteLine($"InnerException StackTrace: {ex.InnerException.StackTrace}");
|
break;
|
||||||
}
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
//File reached a corrupted/non-compliant portion that caused MP3Sharp to throw a NRE.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
totalBytesRead += bytesReturned;
|
||||||
|
await pcmStream.WriteAsync(buffer, 0, bytesReturned, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
pcmData = ResampleToMono(pcmStream.ToArray());
|
pcmData = ResampleToMono(pcmStream.ToArray());
|
||||||
|
|
||||||
var audioDuration = (double)(pcmData.Length / 2) / mp3Stream.Frequency;
|
var audioDuration = (double)(pcmData.Length / 2) / mp3Stream.Frequency;
|
||||||
|
|
||||||
return new AudioFile() { Name = baseFile.Name, MP3Data = file, Data = pcmData, Duration = TimeSpan.FromSeconds(audioDuration), Format = AudioFormat.Mono16, Frequency = mp3Stream.Frequency };
|
return new AudioFile() { Name = baseFile.Name, MP3Data = mp3Input, Data = pcmData, Duration = TimeSpan.FromSeconds(audioDuration), Format = AudioFormat.Mono16, Frequency = mp3Stream.Frequency };
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
private static byte[] ResampleToMono(Span<byte> data)
|
|
||||||
{
|
{
|
||||||
byte[] newData = new byte[data.Length / 2];
|
//An exception here would be something new.
|
||||||
|
|
||||||
for (int i = 0; i < data.Length / 4; ++i)
|
|
||||||
{
|
|
||||||
int HI = 1;
|
|
||||||
int LO = 0;
|
|
||||||
short left = (short)((data[i * 4 + HI] << 8) | (data[i * 4 + LO] & 0xff));
|
|
||||||
short right = (short)((data[i * 4 + 2 + HI] << 8) | (data[i * 4 + 2 + LO] & 0xff));
|
|
||||||
int avg = (left + right) / 2;
|
|
||||||
|
|
||||||
newData[i * 2 + HI] = (byte)((avg >> 8) & 0xff);
|
|
||||||
newData[i * 2 + LO] = (byte)((avg & 0xff));
|
|
||||||
}
|
|
||||||
|
|
||||||
return newData;
|
|
||||||
}
|
}
|
||||||
|
return new AudioFile() { Name = $"${baseFile.Name} failed extraction", Data = ReadOnlyMemory<byte>.Empty, MP3Data = ReadOnlyMemory<byte>.Empty, Duration = TimeSpan.FromSeconds(0), Format = AudioFormat.Unknown, Frequency = 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is pretty cursed, but it works.
|
||||||
|
I don't remember where I got it from the first time, though. I think LLMs weren't a thing yet.
|
||||||
|
*/
|
||||||
|
private static byte[] ResampleToMono(Span<byte> data)
|
||||||
|
{
|
||||||
|
byte[] newData = new byte[data.Length / 2];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.Length / 4; ++i)
|
||||||
|
{
|
||||||
|
int HI = 1;
|
||||||
|
int LO = 0;
|
||||||
|
short left = (short)((data[i * 4 + HI] << 8) | (data[i * 4 + LO] & 0xff));
|
||||||
|
short right = (short)((data[i * 4 + 2 + HI] << 8) | (data[i * 4 + 2 + LO] & 0xff));
|
||||||
|
int avg = (left + right) / 2;
|
||||||
|
|
||||||
|
newData[i * 2 + HI] = (byte)((avg >> 8) & 0xff);
|
||||||
|
newData[i * 2 + LO] = (byte)((avg & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
return newData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
using OpenTK.Audio.OpenAL;
|
using OpenTK.Audio.OpenAL;
|
||||||
using Plpext.Core.Interfaces;
|
using Plpext.Core.Interfaces;
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace Plpext.Core.AudioPlayer;
|
namespace Plpext.Core.AudioPlayer;
|
||||||
|
|
||||||
|
@ -35,19 +28,18 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
{
|
{
|
||||||
int bufferId = AL.GenBuffer();
|
int bufferId = AL.GenBuffer();
|
||||||
int sourceId = AL.GenSource();
|
int sourceId = AL.GenSource();
|
||||||
|
|
||||||
_currentBufferId = bufferId;
|
_currentBufferId = bufferId;
|
||||||
_currentSourceId = sourceId;
|
_currentSourceId = sourceId;
|
||||||
|
|
||||||
AL.BufferData(bufferId, ALFormat.Mono16, input.Data.Span, input.Frequency);
|
AL.BufferData(bufferId, ALFormat.Mono16, input.Data.Span, input.Frequency);
|
||||||
AL.Source(sourceId, ALSourcei.Buffer, bufferId);
|
AL.Source(sourceId, ALSourcei.Buffer, bufferId);
|
||||||
if(autoStart)
|
if (autoStart)
|
||||||
return await Start();
|
return await Start();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"init error: {ex}");
|
|
||||||
await CleanupPlaybackResources();
|
await CleanupPlaybackResources();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +55,6 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var sourceState = (ALSourceState)AL.GetSource(_currentSourceId.Value, ALGetSourcei.SourceState);
|
var sourceState = (ALSourceState)AL.GetSource(_currentSourceId.Value, ALGetSourcei.SourceState);
|
||||||
Console.WriteLine($"source state: {sourceState}");
|
|
||||||
if (sourceState == ALSourceState.Stopped)
|
if (sourceState == ALSourceState.Stopped)
|
||||||
break;
|
break;
|
||||||
if (State != PlaybackState.Paused)
|
if (State != PlaybackState.Paused)
|
||||||
|
@ -73,8 +64,6 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
((double)(_audioFile.Data.Length - (_audioFile.Data.Length - bytesPlayed)) / 2) / _audioFile.Frequency
|
((double)(_audioFile.Data.Length - (_audioFile.Data.Length - bytesPlayed)) / 2) / _audioFile.Frequency
|
||||||
);
|
);
|
||||||
|
|
||||||
Console.WriteLine($"Progress: {currentPosition.TotalSeconds:F2}s / {_audioFile.Duration.TotalSeconds:F2}s");
|
|
||||||
|
|
||||||
OnProgressUpdated?.Invoke(this, new()
|
OnProgressUpdated?.Invoke(this, new()
|
||||||
{
|
{
|
||||||
CurrentPosition = currentPosition,
|
CurrentPosition = currentPosition,
|
||||||
|
@ -86,15 +75,14 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Playback cancelled");
|
//An operation cancelled here is expected behavior.
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Monitor error: {ex}");
|
//There is little to be done if we get any other exception during monitoring. Finish up playback monitoring.
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Console.WriteLine("Monitor task ending");
|
|
||||||
OnProgressUpdated?.Invoke(this, new()
|
OnProgressUpdated?.Invoke(this, new()
|
||||||
{
|
{
|
||||||
CurrentPosition = _audioFile.Duration,
|
CurrentPosition = _audioFile.Duration,
|
||||||
|
@ -108,9 +96,9 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
|
//If we try and start/resume playback without a proper source id or audio file, we can't go on.
|
||||||
if (_currentSourceId == null || _audioFile == null)
|
if (_currentSourceId == null || _audioFile == null)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Cannot start - no source or audio file");
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,16 +125,13 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
State = PlaybackState.Playing;
|
State = PlaybackState.Playing;
|
||||||
OnPlaybackStarted?.Invoke(this, new PlaybackStartedEventArgs { AudioFile = _audioFile });
|
OnPlaybackStarted?.Invoke(this, new PlaybackStartedEventArgs { AudioFile = _audioFile });
|
||||||
|
|
||||||
Console.WriteLine($"Rewind");
|
|
||||||
AL.SourceRewind(_currentSourceId.Value);
|
AL.SourceRewind(_currentSourceId.Value);
|
||||||
Console.WriteLine($"Play");
|
|
||||||
AL.SourcePlay(_currentSourceId.Value);
|
AL.SourcePlay(_currentSourceId.Value);
|
||||||
_playbackTask = Task.Run(() => MonitorPlaybackAsync(_playbackCts.Token));
|
_playbackTask = Task.Run(() => MonitorPlaybackAsync(_playbackCts.Token));
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Start error: {ex}");
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +162,6 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
Console.WriteLine($"STOP called");
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_playbackCts?.Cancel();
|
_playbackCts?.Cancel();
|
||||||
|
@ -190,7 +174,8 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
_playbackCts?.Cancel();
|
_playbackCts?.Cancel();
|
||||||
State = PlaybackState.Stopped;
|
State = PlaybackState.Stopped;
|
||||||
}
|
}
|
||||||
OnPlaybackStopped?.Invoke(this, new() { AudioFile = _audioFile });
|
if(_audioFile is not null)
|
||||||
|
OnPlaybackStopped?.Invoke(this, new() { AudioFile = _audioFile });
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task CleanupPlaybackResources()
|
private Task CleanupPlaybackResources()
|
||||||
|
@ -209,9 +194,9 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
||||||
_currentBufferId = null;
|
_currentBufferId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error during cleanup: {ex}");
|
//Things might be pretty broken at this point if this exception pops.
|
||||||
}
|
}
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,41 @@
|
||||||
using Plpext.Core.Interfaces;
|
using Plpext.Core.Interfaces;
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.FileStorage
|
namespace Plpext.Core.FileStorage;
|
||||||
|
|
||||||
|
public class DiskFileStorage : IFileStorage
|
||||||
{
|
{
|
||||||
public class DiskFileStorage : IFileStorage
|
public async Task SaveFilesAsync(IEnumerable<MP3File> files, string targetPath)
|
||||||
{
|
{
|
||||||
public async Task SaveFilesAsync(IEnumerable<MP3File> files, string targetPath)
|
HashSet<string> names = [];
|
||||||
|
try
|
||||||
{
|
{
|
||||||
HashSet<string> names = new HashSet<string>();
|
if (!Directory.Exists(targetPath))
|
||||||
|
Directory.CreateDirectory(targetPath);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
var finalFileName = TryAddName(names, string.Join("_", file.Name.Split(Path.GetInvalidFileNameChars())));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(targetPath))
|
await File.Create(Path.Combine(targetPath, $"{finalFileName}.mp3")).WriteAsync(file.Data);
|
||||||
Directory.CreateDirectory(targetPath);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Debug.WriteLine(e);
|
//We can proceed with the next ones.
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
|
||||||
var finalFileName = TryAddName(names, string.Join("_", file.Name.Split(Path.GetInvalidFileNameChars())));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await File.Create(Path.Combine(targetPath, $"{finalFileName}.mp3")).WriteAsync(file.Data);
|
|
||||||
}
|
|
||||||
catch (Exception e) { Debug.WriteLine(e); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static string TryAddName(HashSet<string> names, string newName)
|
|
||||||
{
|
|
||||||
if (!names.Add(newName))
|
|
||||||
return TryAddName(names, newName + "_");
|
|
||||||
return newName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private static string TryAddName(HashSet<string> names, string newName)
|
||||||
|
{
|
||||||
|
if (!names.Add(newName))
|
||||||
|
return TryAddName(names, newName + "_");
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Interfaces;
|
namespace Plpext.Core.Interfaces;
|
||||||
public interface IAudioConverter
|
public interface IAudioConverter
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Interfaces;
|
namespace Plpext.Core.Interfaces;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Interfaces
|
namespace Plpext.Core.Interfaces;
|
||||||
|
|
||||||
|
public interface IFileStorage
|
||||||
{
|
{
|
||||||
public interface IFileStorage
|
Task SaveFilesAsync(IEnumerable<MP3File> files, string targetPath);
|
||||||
{
|
|
||||||
Task SaveFilesAsync(IEnumerable<MP3File> files, string targetPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Interfaces
|
namespace Plpext.Core.Interfaces;
|
||||||
|
|
||||||
|
public interface IMP3Parser
|
||||||
{
|
{
|
||||||
public interface IMP3Parser
|
Task<MP3File> ParseIntoMP3(ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
|
||||||
{
|
|
||||||
Task<MP3File> ParseIntoMP3(ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
using System;
|
namespace Plpext.Core.Interfaces;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Interfaces;
|
|
||||||
|
|
||||||
public interface IPackExtractor
|
public interface IPackExtractor
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
using MP3Sharp;
|
using Plpext.Core.Interfaces;
|
||||||
using Plpext.Core.Interfaces;
|
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.MP3Parser
|
namespace Plpext.Core.MP3Parser;
|
||||||
|
|
||||||
|
public class MP3Parser : IMP3Parser
|
||||||
{
|
{
|
||||||
public class MP3Parser : IMP3Parser
|
public Task<MP3File> ParseIntoMP3(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
public Task<MP3File> ParseIntoMP3(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
|
var fileName = Encoding.Latin1.GetString(data.Slice(start: 24, length: 260).Span).Split('\0')[0].Normalize().Trim();
|
||||||
{
|
var fileData = data[284..];
|
||||||
var fileName = Encoding.Latin1.GetString(data.Slice(start: 24, length: 260).Span).Split('\0')[0].Normalize().Trim();
|
return Task.FromResult(new MP3File() { Name = fileName, Data = fileData });
|
||||||
var fileData = data[284..];
|
|
||||||
return Task.FromResult(new MP3File() { Name = fileName, Data = fileData });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,18 +1,11 @@
|
||||||
using System;
|
namespace Plpext.Core.Models;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Models
|
public record AudioFile
|
||||||
{
|
{
|
||||||
public record AudioFile
|
public required string Name { get; init; }
|
||||||
{
|
public ReadOnlyMemory<byte> MP3Data { get; init; }
|
||||||
public required string Name { get; init; }
|
public ReadOnlyMemory<byte> Data { get; init; }
|
||||||
public ReadOnlyMemory<byte> MP3Data { get; init; }
|
public TimeSpan Duration { get; init; }
|
||||||
public ReadOnlyMemory<byte> Data { get; init; }
|
public AudioFormat Format { get; init; }
|
||||||
public TimeSpan Duration { get; init; }
|
public int Frequency { get; init; }
|
||||||
public AudioFormat Format { get; init; }
|
|
||||||
public int Frequency { get; init; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
using System;
|
namespace Plpext.Core.Models;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Models;
|
|
||||||
|
|
||||||
public enum AudioFormat
|
public enum AudioFormat
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
using System;
|
namespace Plpext.Core.Models;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Models;
|
|
||||||
|
|
||||||
public class PlaybackStoppedEventArgs : EventArgs
|
public class PlaybackStoppedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public AudioFile AudioFile { get; init; }
|
public required AudioFile AudioFile { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PlaybackStartedEventArgs : EventArgs
|
public class PlaybackStartedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public AudioFile AudioFile { get; init; }
|
public required AudioFile AudioFile { get; init; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
using System;
|
namespace Plpext.Core.Models;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Models
|
public record MP3File
|
||||||
{
|
{
|
||||||
public record MP3File
|
public required string Name { get; init; }
|
||||||
{
|
public ReadOnlyMemory<byte> Data { get; init; }
|
||||||
public required string Name { get; init; }
|
|
||||||
public ReadOnlyMemory<byte> Data { get; init; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
using System;
|
namespace Plpext.Core.Models;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.Models
|
public record PlaybackProgress
|
||||||
{
|
{
|
||||||
public record PlaybackProgress
|
public TimeSpan CurrentPosition { get; init; }
|
||||||
{
|
public TimeSpan TotalDuration { get; init; }
|
||||||
public TimeSpan CurrentPosition { get; init; }
|
public double ProgressPercentage => TotalDuration.TotalSeconds > 0 ? (CurrentPosition.TotalSeconds / TotalDuration.TotalSeconds) * 100 : 0;
|
||||||
public TimeSpan TotalDuration { get; init; }
|
}
|
||||||
public double ProgressPercentage => TotalDuration.TotalSeconds > 0 ? (CurrentPosition.TotalSeconds / TotalDuration.TotalSeconds) * 100 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,5 @@
|
||||||
using Plpext.Core.Interfaces;
|
using Plpext.Core.Interfaces;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Plpext.Core.PackExtractor
|
namespace Plpext.Core.PackExtractor
|
||||||
{
|
{
|
||||||
|
@ -13,24 +8,16 @@ namespace Plpext.Core.PackExtractor
|
||||||
private static readonly byte[] filePattern = { 0x53, 0x4E, 0x44, 0x55, 0x00 };
|
private static readonly byte[] filePattern = { 0x53, 0x4E, 0x44, 0x55, 0x00 };
|
||||||
public async Task<IEnumerable<ReadOnlyMemory<byte>>> GetFileListAsync(string filePath, CancellationToken cancellationToken)
|
public async Task<IEnumerable<ReadOnlyMemory<byte>>> GetFileListAsync(string filePath, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
ReadOnlyMemory<byte> file = await File.ReadAllBytesAsync(filePath, cancellationToken);
|
||||||
{
|
var fileIndexes = FindFileIndexes(file.Span);
|
||||||
ReadOnlyMemory<byte> file = await File.ReadAllBytesAsync(filePath, cancellationToken);
|
|
||||||
var fileIndexes = FindFileIndexes(file.Span);
|
|
||||||
|
|
||||||
var result = new List<ReadOnlyMemory<byte>>();
|
var result = new List<ReadOnlyMemory<byte>>();
|
||||||
for(int i = 0; i < fileIndexes.Count - 1; ++i)
|
for (int i = 0; i < fileIndexes.Count - 1; ++i)
|
||||||
{
|
|
||||||
var nextFile = file.Slice(start: fileIndexes[i], length: fileIndexes[i + 1] - fileIndexes[i]);
|
|
||||||
result.Add(nextFile);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
{
|
||||||
Debug.WriteLine(e);
|
var nextFile = file.Slice(start: fileIndexes[i], length: fileIndexes[i + 1] - fileIndexes[i]);
|
||||||
throw;
|
result.Add(nextFile);
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<int> FindFileIndexes(ReadOnlySpan<byte> file)
|
private static List<int> FindFileIndexes(ReadOnlySpan<byte> file)
|
||||||
|
@ -54,7 +41,9 @@ namespace Plpext.Core.PackExtractor
|
||||||
idx += filePattern.Length;
|
idx += filePattern.Length;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
idx += skipTable[file[idx + j]];
|
idx += skipTable[file[idx + j]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result.Add(file.Length - 1);
|
result.Add(file.Length - 1);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
x:Class="Plpext.UI.App"
|
||||||
x:Class="Plpext.UI.App"
|
xmlns="https://github.com/avaloniaui"
|
||||||
RequestedThemeVariant="Dark">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
RequestedThemeVariant="Dark">
|
||||||
|
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceInclude Source="avares://Plpext/Resources/Colors.axaml"/>
|
<ResourceInclude Source="avares://Plpext/Resources/Colors.axaml" />
|
||||||
<ResourceInclude Source="avares://Plpext/Controls/AudioPlayerControl.axaml"/>
|
<ResourceInclude Source="avares://Plpext/Controls/AudioPlayerControl.axaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<FluentTheme />
|
||||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
|
||||||
<StyleInclude Source="avares://Plpext/Styles/AudioPlayerControl.axaml"/>
|
<StyleInclude Source="avares://Plpext/Styles/AudioPlayerControl.axaml" />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
|
@ -2,81 +2,102 @@
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:controls="using:Plpext.UI"
|
xmlns:controls="using:Plpext.UI"
|
||||||
xmlns:vm="clr-namespace:Plpext.UI.ViewModels"
|
xmlns:cv="clr-namespace:Plpext.UI.Controls.Converters"
|
||||||
xmlns:cv="clr-namespace:Plpext.UI.Controls.Converters">
|
xmlns:vm="clr-namespace:Plpext.UI.ViewModels">
|
||||||
|
|
||||||
<!--
|
|
||||||
Additional resources
|
|
||||||
Using Control Themes:
|
|
||||||
https://docs.avaloniaui.net/docs/basics/user-interface/styling/control-themes
|
|
||||||
Using Theme Variants:
|
|
||||||
https://docs.avaloniaui.net/docs/guides/styles-and-resources/how-to-use-theme-variants
|
|
||||||
-->
|
|
||||||
|
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Padding="10" Background="Brown">
|
<Border Padding="10" Background="Brown">
|
||||||
<StackPanel Width="200" Spacing="10">
|
<StackPanel Width="200" Spacing="10">
|
||||||
<StackPanel Background="{DynamicResource SystemRegionBrush}">
|
<StackPanel Background="{DynamicResource SystemRegionBrush}">
|
||||||
<controls:AudioPlayerControl IsPlaying="True" PlaybackState="Stopped" />
|
<controls:AudioPlayerControl IsPlaying="True" PlaybackState="Stopped" />
|
||||||
<controls:AudioPlayerControl IsPlaying="True" PlaybackState="Paused" />
|
<controls:AudioPlayerControl IsPlaying="True" PlaybackState="Paused" />
|
||||||
<controls:AudioPlayerControl IsPlaying="True" PlaybackState="Playing" />
|
<controls:AudioPlayerControl IsPlaying="True" PlaybackState="Playing" />
|
||||||
<controls:AudioPlayerControl IsPlaying="False" PlaybackState="Stopped" />
|
<controls:AudioPlayerControl IsPlaying="False" PlaybackState="Stopped" />
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
|
|
||||||
<ControlTheme x:Key="{x:Type controls:AudioPlayerControl}" TargetType="controls:AudioPlayerControl"
|
<ControlTheme
|
||||||
x:DataType="vm:AudioPlayerViewModel">
|
x:Key="{x:Type controls:AudioPlayerControl}"
|
||||||
|
x:DataType="vm:AudioPlayerViewModel"
|
||||||
|
TargetType="controls:AudioPlayerControl">
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
<Grid Name="ButtonGrid" ColumnDefinitions="*,*, Auto">
|
<Grid Name="ButtonGrid" ColumnDefinitions="*,*, Auto">
|
||||||
<Grid.Transitions>
|
<Grid.Transitions>
|
||||||
<Transitions>
|
<Transitions>
|
||||||
<ThicknessTransition Property="Margin" Duration="0:0:0.3" Easing="SineEaseInOut"/>
|
<ThicknessTransition
|
||||||
|
Easing="SineEaseInOut"
|
||||||
|
Property="Margin"
|
||||||
|
Duration="0:0:0.3" />
|
||||||
</Transitions>
|
</Transitions>
|
||||||
</Grid.Transitions>
|
</Grid.Transitions>
|
||||||
<Button Name="PlayButton" Grid.Column="0" IsEnabled="{TemplateBinding IsEnabled}"
|
<Button
|
||||||
HorizontalAlignment="Center" Width="32" Height="32"
|
Name="PlayButton"
|
||||||
Padding="6 6 6 6"
|
Grid.Column="0"
|
||||||
CornerRadius="12"
|
Width="32"
|
||||||
FontSize="{TemplateBinding FontSize}"
|
Height="32"
|
||||||
CommandParameter="{TemplateBinding PlayCommandParameter}" Command="{TemplateBinding PlayCommand}">
|
Padding="6,6,6,6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Command="{TemplateBinding PlayCommand}"
|
||||||
|
CommandParameter="{TemplateBinding PlayCommandParameter}"
|
||||||
|
CornerRadius="12"
|
||||||
|
FontSize="{TemplateBinding FontSize}"
|
||||||
|
IsEnabled="{TemplateBinding IsEnabled}">
|
||||||
<Path Fill="{Binding $parent[Button].Foreground}">
|
<Path Fill="{Binding $parent[Button].Foreground}">
|
||||||
<Path.Data>
|
<Path.Data>
|
||||||
<Binding Path="PlaybackState"
|
<Binding
|
||||||
RelativeSource="{RelativeSource TemplatedParent}"
|
Converter="{x:Static cv:PlaybackStateToPathConverter.Instance}"
|
||||||
Converter="{x:Static cv:PlaybackStateToPathConverter.Instance}"/>
|
Path="PlaybackState"
|
||||||
|
RelativeSource="{RelativeSource TemplatedParent}" />
|
||||||
</Path.Data>
|
</Path.Data>
|
||||||
</Path>
|
</Path>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Name="StopButton" Grid.Column="1" IsEnabled="{TemplateBinding IsEnabled}" IsVisible="{TemplateBinding IsPlaying}"
|
<Button
|
||||||
HorizontalAlignment="Center" Width="32" Height="32"
|
Name="StopButton"
|
||||||
Padding="6 6 6 6"
|
Grid.Column="1"
|
||||||
CornerRadius="12"
|
Width="32"
|
||||||
FontSize="{TemplateBinding FontSize}"
|
Height="32"
|
||||||
CommandParameter="{TemplateBinding StopCommandParameter}" Command="{TemplateBinding StopCommand}">
|
Padding="6,6,6,6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Command="{TemplateBinding StopCommand}"
|
||||||
|
CommandParameter="{TemplateBinding StopCommandParameter}"
|
||||||
|
CornerRadius="12"
|
||||||
|
FontSize="{TemplateBinding FontSize}"
|
||||||
|
IsEnabled="{TemplateBinding IsEnabled}"
|
||||||
|
IsVisible="{TemplateBinding IsPlaying}">
|
||||||
<Path Data="M2,2 H14 V14 H2 Z" Fill="{Binding $parent[Button].Foreground}" />
|
<Path Data="M2,2 H14 V14 H2 Z" Fill="{Binding $parent[Button].Foreground}" />
|
||||||
</Button>
|
</Button>
|
||||||
<StackPanel Grid.Column="2" Name="ProgressPanel" Orientation="Vertical">
|
<StackPanel
|
||||||
|
Name="ProgressPanel"
|
||||||
|
Grid.Column="2"
|
||||||
|
Orientation="Vertical">
|
||||||
<StackPanel.Transitions>
|
<StackPanel.Transitions>
|
||||||
<Transitions>
|
<Transitions>
|
||||||
<DoubleTransition Property="Width" Duration="0:0:0.3" Easing="SineEaseInOut"/>
|
<DoubleTransition
|
||||||
<DoubleTransition Property="Opacity" Duration="0:0:0.3"/>
|
Easing="SineEaseInOut"
|
||||||
|
Property="Width"
|
||||||
|
Duration="0:0:0.3" />
|
||||||
|
<DoubleTransition Property="Opacity" Duration="0:0:0.3" />
|
||||||
</Transitions>
|
</Transitions>
|
||||||
</StackPanel.Transitions>
|
</StackPanel.Transitions>
|
||||||
<ProgressBar Name="ProgressPanelBar" Value="{TemplateBinding Progress}" HorizontalAlignment="Left" CornerRadius="2"/>
|
<ProgressBar
|
||||||
|
Name="ProgressPanelBar"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
CornerRadius="2"
|
||||||
|
Value="{TemplateBinding Progress}" />
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Label FontSize="{TemplateBinding FontSize}" Content="{TemplateBinding CurrentDuration}"/>
|
<Label Content="{TemplateBinding CurrentDuration}" FontSize="{TemplateBinding FontSize}" />
|
||||||
<Label FontSize="{TemplateBinding FontSize}" Content="/"/>
|
<Label Content="/" FontSize="{TemplateBinding FontSize}" />
|
||||||
<Label FontSize="{TemplateBinding FontSize}" Content="{TemplateBinding TotalDuration}"/>
|
<Label Content="{TemplateBinding TotalDuration}" FontSize="{TemplateBinding FontSize}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
|
|
||||||
|
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
|
@ -7,10 +7,6 @@ using Plpext.Core.PackExtractor;
|
||||||
using Plpext.UI.ViewModels;
|
using Plpext.UI.ViewModels;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Plpext.Core.AudioConverter;
|
using Plpext.Core.AudioConverter;
|
||||||
using Plpext.Core.AudioPlayer;
|
using Plpext.Core.AudioPlayer;
|
||||||
using Plpext.UI.Services;
|
using Plpext.UI.Services;
|
||||||
|
@ -19,45 +15,42 @@ using Plpext.UI.Services.FileLoader;
|
||||||
using Plpext.UI.Services.PlatformStorage;
|
using Plpext.UI.Services.PlatformStorage;
|
||||||
using Plpext.UI.Views;
|
using Plpext.UI.Views;
|
||||||
|
|
||||||
namespace Plpext.UI.DependencyInjection
|
namespace Plpext.UI.DependencyInjection;
|
||||||
|
|
||||||
|
public static class Container
|
||||||
{
|
{
|
||||||
public static class Container
|
private static IServiceProvider? _container;
|
||||||
|
public static IServiceProvider Services
|
||||||
{
|
{
|
||||||
private static IServiceProvider? _container;
|
get => _container ?? Register();
|
||||||
public static IServiceProvider Services
|
}
|
||||||
{
|
|
||||||
get => _container ?? Register();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IServiceProvider Register()
|
private static IServiceProvider Register()
|
||||||
{
|
{
|
||||||
var hostBuilder = Host
|
var hostBuilder = Host
|
||||||
.CreateDefaultBuilder()
|
.CreateDefaultBuilder()
|
||||||
.UseSerilog((context, loggerConfiguration) =>
|
.UseSerilog((context, loggerConfiguration) =>
|
||||||
{
|
{
|
||||||
loggerConfiguration.WriteTo.Debug();
|
loggerConfiguration.WriteTo.Debug();
|
||||||
})
|
})
|
||||||
.ConfigureServices((context, services) =>
|
.ConfigureServices((context, services) =>
|
||||||
{
|
{
|
||||||
services.AddSingleton<MainWindow>();
|
services.AddSingleton<MainWindow>();
|
||||||
services.AddSingleton<MainWindowViewModel>();
|
services.AddSingleton<MainWindowViewModel>();
|
||||||
|
|
||||||
services.AddTransient<AudioPlayerViewModel>();
|
services.AddSingleton<AudioContext>();
|
||||||
|
services.AddScoped<IAudioPlayer, AudioPlayer>();
|
||||||
services.AddSingleton<AudioContext>();
|
services.AddSingleton<IAudioConverter, MP3AudioConverter>();
|
||||||
services.AddScoped<IAudioPlayer, AudioPlayer>();
|
services.AddSingleton<IMP3Parser, MP3Parser>();
|
||||||
services.AddSingleton<IAudioConverter, MP3AudioConverter>();
|
services.AddSingleton<IPackExtractor, CorePackExtractor>();
|
||||||
services.AddSingleton<IMP3Parser, MP3Parser>();
|
services.AddSingleton<IFileStorage, DiskFileStorage>();
|
||||||
services.AddSingleton<IPackExtractor, CorePackExtractor>();
|
services.AddSingleton<IPlatformStorageService, PlatformStorageService>();
|
||||||
services.AddSingleton<IFileStorage, DiskFileStorage>();
|
services.AddSingleton<IFileLoaderService, FileLoaderService>();
|
||||||
services.AddSingleton<IPlatformStorageService, PlatformStorageService>();
|
services.AddSingleton<IConvertService, FileConvertService>();
|
||||||
services.AddSingleton<IFileLoaderService, FileLoaderService>();
|
})
|
||||||
services.AddSingleton<IConvertService, FileConvertService>();
|
.Build();
|
||||||
})
|
hostBuilder.Start();
|
||||||
.Build();
|
_container = hostBuilder.Services;
|
||||||
hostBuilder.Start();
|
return _container;
|
||||||
_container = hostBuilder.Services;
|
|
||||||
return _container;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,16 @@
|
||||||
<PublishReadyToRun>true</PublishReadyToRun>
|
<PublishReadyToRun>true</PublishReadyToRun>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
||||||
|
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Models\" />
|
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
<AvaloniaResource Remove="ViewModels\Mocks\**" />
|
<AvaloniaResource Remove="ViewModels\Mocks\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -45,15 +51,21 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="ViewModels\Mocks\MockAudioPlayerViewModel.cs" />
|
<Compile Remove="ViewModels\Mocks\MockAudioPlayerViewModel.cs" />
|
||||||
|
<Compile Remove="publish\**" />
|
||||||
|
<Compile Remove="Releases\**" />
|
||||||
<Compile Remove="ViewModels\Mocks\**" />
|
<Compile Remove="ViewModels\Mocks\**" />
|
||||||
<Compile Remove="ViewLocator.cs" />
|
<Compile Remove="ViewLocator.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<AvaloniaXaml Remove="publish\**" />
|
||||||
|
<AvaloniaXaml Remove="Releases\**" />
|
||||||
<AvaloniaXaml Remove="ViewModels\Mocks\**" />
|
<AvaloniaXaml Remove="ViewModels\Mocks\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Remove="publish\**" />
|
||||||
|
<EmbeddedResource Remove="Releases\**" />
|
||||||
<EmbeddedResource Remove="ViewModels\Mocks\**" />
|
<EmbeddedResource Remove="ViewModels\Mocks\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -62,13 +74,15 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="publish\**" />
|
||||||
|
<None Remove="Releases\**" />
|
||||||
<None Remove="ViewModels\Mocks\**" />
|
<None Remove="ViewModels\Mocks\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'win-x86'">
|
<ItemGroup>
|
||||||
<OpenAL32Dll Include="$(MSBuildProjectDirectory)\..\..\..\deps\win\x86\OpenAL32.dll" Condition="'$(RuntimeIdentifier)' == 'win-x86'" />
|
<OpenAL32Dll Include="$(MSBuildProjectDirectory)\..\..\..\deps\win\x86\OpenAL32.dll" Condition="'$(RuntimeIdentifier)' == 'win-x86' or '$(PlatformTarget)' == 'x86'" />
|
||||||
<OpenAL32Dll Include="$(MSBuildProjectDirectory)\..\..\..\deps\win\x64\OpenAL32.dll" Condition="'$(RuntimeIdentifier)' == 'win-x64'" />
|
<OpenAL32Dll Include="$(MSBuildProjectDirectory)\..\..\..\deps\win\x64\OpenAL32.dll" Condition="'$(RuntimeIdentifier)' == 'win-x64' or '$(PlatformTarget)' == 'x64'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Copy SourceFiles="@(OpenAL32Dll)" DestinationFiles="$(OutputPath)\OpenAL32.dll" />
|
<Copy SourceFiles="@(OpenAL32Dll)" DestinationFiles="$(OutputPath)\OpenAL32.dll" />
|
||||||
</Target>
|
</Target>
|
||||||
|
@ -78,6 +92,6 @@
|
||||||
<OpenAL32Dll Include="$(MSBuildProjectDirectory)\..\..\..\deps\win\x86\OpenAL32.dll" Condition="'$(RuntimeIdentifier)' == 'win-x86'" />
|
<OpenAL32Dll Include="$(MSBuildProjectDirectory)\..\..\..\deps\win\x86\OpenAL32.dll" Condition="'$(RuntimeIdentifier)' == 'win-x86'" />
|
||||||
<OpenAL32Dll Include="$(MSBuildProjectDirectory)\..\..\..\deps\win\x64\OpenAL32.dll" Condition="'$(RuntimeIdentifier)' == 'win-x64'" />
|
<OpenAL32Dll Include="$(MSBuildProjectDirectory)\..\..\..\deps\win\x64\OpenAL32.dll" Condition="'$(RuntimeIdentifier)' == 'win-x64'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Copy SourceFiles="@(OpenAL32Dll)" DestinationFiles="$(PublishDir)\OpenAL32.dll"/>
|
<Copy SourceFiles="@(OpenAL32Dll)" DestinationFiles="$(PublishDir)\OpenAL32.dll" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -2,25 +2,24 @@
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Velopack;
|
using Velopack;
|
||||||
|
|
||||||
namespace Plpext.UI
|
namespace Plpext.UI;
|
||||||
{
|
|
||||||
internal sealed class Program
|
|
||||||
{
|
|
||||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
|
||||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
|
||||||
// yet and stuff might break.
|
|
||||||
[STAThread]
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
VelopackApp.Build().Run();
|
|
||||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avalonia configuration, don't remove; also used by visual designer.
|
internal sealed class Program
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
{
|
||||||
=> AppBuilder.Configure<App>()
|
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||||
.UsePlatformDetect()
|
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||||
.WithInterFont()
|
// yet and stuff might break.
|
||||||
.LogToTrace();
|
[STAThread]
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
VelopackApp.Build().Run();
|
||||||
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.WithInterFont()
|
||||||
|
.LogToTrace();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Plpext.Core.AudioPlayer;
|
using Plpext.Core.AudioPlayer;
|
||||||
using Plpext.Core.Interfaces;
|
using Plpext.Core.Interfaces;
|
||||||
using Plpext.Core.Models;
|
|
||||||
using Plpext.UI.ViewModels;
|
using Plpext.UI.ViewModels;
|
||||||
|
|
||||||
namespace Plpext.UI.Services.FileLoader;
|
namespace Plpext.UI.Services.FileLoader;
|
||||||
|
@ -14,7 +13,7 @@ public class FileLoaderService : IFileLoaderService
|
||||||
private readonly IPackExtractor _packExtractor;
|
private readonly IPackExtractor _packExtractor;
|
||||||
private readonly IAudioConverter _audioConverter;
|
private readonly IAudioConverter _audioConverter;
|
||||||
private IEnumerable<ReadOnlyMemory<byte>> _currentFile = null!;
|
private IEnumerable<ReadOnlyMemory<byte>> _currentFile = null!;
|
||||||
|
|
||||||
public FileLoaderService(IPackExtractor packExtractor, IAudioConverter audioConverter)
|
public FileLoaderService(IPackExtractor packExtractor, IAudioConverter audioConverter)
|
||||||
{
|
{
|
||||||
_packExtractor = packExtractor;
|
_packExtractor = packExtractor;
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
|
@ -21,12 +18,12 @@ namespace Plpext.UI.Services.PlatformStorage
|
||||||
{
|
{
|
||||||
AllowMultiple = false,
|
AllowMultiple = false,
|
||||||
Title = "Select Plus Library Pack",
|
Title = "Select Plus Library Pack",
|
||||||
FileTypeFilter = [new ("Plus Library Pack"){Patterns = ["*.plp"]}],
|
FileTypeFilter = [new("Plus Library Pack") { Patterns = ["*.plp"] }],
|
||||||
});
|
});
|
||||||
|
|
||||||
return filePath.Any() ? filePath[0].Path.AbsolutePath : string.Empty;
|
return filePath.Any() ? filePath[0].Path.AbsolutePath : string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetTargetFolderPath()
|
public async Task<string> GetTargetFolderPath()
|
||||||
{
|
{
|
||||||
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop ||
|
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop ||
|
||||||
|
|
|
@ -1,86 +1,92 @@
|
||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:c="clr-namespace:Plpext.UI"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:c="clr-namespace:Plpext.UI"
|
||||||
xmlns:vm="clr-namespace:Plpext.UI.ViewModels">
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:vm="clr-namespace:Plpext.UI.ViewModels">
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Padding="20" Background="RoyalBlue" BorderBrush="Black" BorderThickness="1">
|
<Border
|
||||||
|
Padding="20"
|
||||||
|
Background="RoyalBlue"
|
||||||
|
BorderBrush="Black"
|
||||||
|
BorderThickness="1">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Border Background="Navy" BorderBrush="White" BorderThickness="1">
|
<Border
|
||||||
<c:AudioPlayerControl
|
Background="Navy"
|
||||||
x:DataType="vm:AudioPlayerViewModel"
|
BorderBrush="White"
|
||||||
IsPlaying="{Binding IsPlaying}"
|
BorderThickness="1">
|
||||||
PlayCommand="{Binding PlayCommand}"
|
<c:AudioPlayerControl
|
||||||
StopCommand="{Binding StopCommand}"
|
x:DataType="vm:AudioPlayerViewModel"
|
||||||
PlaybackState="{Binding PlaybackState}"
|
CurrentDuration="0:15"
|
||||||
TotalDuration="0:22"
|
IsPlaying="{Binding IsPlaying}"
|
||||||
CurrentDuration="0:15"
|
PlayCommand="{Binding PlayCommand}"
|
||||||
Progress="44.8"
|
PlaybackState="{Binding PlaybackState}"
|
||||||
>
|
Progress="44.8"
|
||||||
<c:AudioPlayerControl.DataContext>
|
StopCommand="{Binding StopCommand}"
|
||||||
<vm:AudioPlayerViewModel/>
|
TotalDuration="0:22">
|
||||||
</c:AudioPlayerControl.DataContext>
|
<c:AudioPlayerControl.DataContext>
|
||||||
</c:AudioPlayerControl>
|
<vm:AudioPlayerViewModel />
|
||||||
</Border>
|
</c:AudioPlayerControl.DataContext>
|
||||||
|
</c:AudioPlayerControl>
|
||||||
|
</Border>
|
||||||
<c:AudioPlayerControl
|
<c:AudioPlayerControl
|
||||||
x:DataType="vm:AudioPlayerViewModel"
|
x:DataType="vm:AudioPlayerViewModel"
|
||||||
|
CurrentDuration="15"
|
||||||
IsPlaying="True"
|
IsPlaying="True"
|
||||||
PlayCommand="{Binding PlayCommand}"
|
PlayCommand="{Binding PlayCommand}"
|
||||||
TotalDuration="22"
|
|
||||||
CurrentDuration="15"
|
|
||||||
Progress="44.8"
|
Progress="44.8"
|
||||||
>
|
TotalDuration="22">
|
||||||
<c:AudioPlayerControl.DataContext>
|
<c:AudioPlayerControl.DataContext>
|
||||||
<vm:AudioPlayerViewModel/>
|
<vm:AudioPlayerViewModel />
|
||||||
</c:AudioPlayerControl.DataContext>
|
</c:AudioPlayerControl.DataContext>
|
||||||
</c:AudioPlayerControl>
|
</c:AudioPlayerControl>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
|
|
||||||
<Style Selector="c|AudioPlayerControl">
|
<Style Selector="c|AudioPlayerControl">
|
||||||
<Setter Property="FontSize" Value="14"/>
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="Margin" Value="0,0,0,0"/>
|
<Setter Property="Margin" Value="0,0,0,0" />
|
||||||
<Setter Property="Width" Value="176"></Setter>
|
<Setter Property="Width" Value="176" />
|
||||||
<Setter Property="Height" Value="36"></Setter>
|
<Setter Property="Height" Value="36" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl Button">
|
<Style Selector="c|AudioPlayerControl Button">
|
||||||
<Setter Property="Foreground" Value="{StaticResource SecondaryLightest}"/>
|
<Setter Property="Foreground" Value="{StaticResource SecondaryLightest}" />
|
||||||
<Setter Property="Background" Value="{StaticResource SecondaryDarkest}"/>
|
<Setter Property="Background" Value="{StaticResource SecondaryDarkest}" />
|
||||||
<Setter Property="BorderThickness" Value="2"/>
|
<Setter Property="BorderThickness" Value="2" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource SecondaryDark}"/>
|
<Setter Property="BorderBrush" Value="{StaticResource SecondaryDark}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl Button:pointerover">
|
<Style Selector="c|AudioPlayerControl Button:pointerover">
|
||||||
<Setter Property="Foreground" Value="{StaticResource SecondaryMiddle}"/>
|
<Setter Property="Foreground" Value="{StaticResource SecondaryMiddle}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl Button:pointerover /template/ ContentPresenter">
|
<Style Selector="c|AudioPlayerControl Button:pointerover /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{StaticResource SecondaryDark}"/>
|
<Setter Property="Background" Value="{StaticResource SecondaryDark}" />
|
||||||
<Setter Property="BorderThickness" Value="2"/>
|
<Setter Property="BorderThickness" Value="2" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource SecondaryDarkest}"/>
|
<Setter Property="BorderBrush" Value="{StaticResource SecondaryDarkest}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ Grid#ButtonGrid">
|
<Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ Grid#ButtonGrid">
|
||||||
<Setter Property="Margin" Value="142,0,-32,0"/>
|
<Setter Property="Margin" Value="142,0,-32,0" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ Grid#ButtonGrid">
|
<Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ Grid#ButtonGrid">
|
||||||
<Setter Property="Margin" Value="0,0,0,0"/>
|
<Setter Property="Margin" Value="0,0,0,0" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ StackPanel#ProgressPanel">
|
<Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ StackPanel#ProgressPanel">
|
||||||
<Setter Property="Width" Value="0"/>
|
<Setter Property="Width" Value="0" />
|
||||||
<Setter Property="Opacity" Value="0"/>
|
<Setter Property="Opacity" Value="0" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ StackPanel#ProgressPanel">
|
<Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ StackPanel#ProgressPanel">
|
||||||
<Setter Property="UseLayoutRounding" Value="True"/>
|
<Setter Property="UseLayoutRounding" Value="True" />
|
||||||
<Setter Property="Opacity" Value="1"/>
|
<Setter Property="Opacity" Value="1" />
|
||||||
<Setter Property="Margin" Value="6,10,0,0"/>
|
<Setter Property="Margin" Value="6,10,0,0" />
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ ProgressBar:horizontal">
|
<Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ ProgressBar:horizontal">
|
||||||
<Setter Property="MinWidth" Value="0"/>
|
<Setter Property="MinWidth" Value="0" />
|
||||||
<Setter Property="Width" Value="0"/>
|
<Setter Property="Width" Value="0" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ ProgressBar:horizontal">
|
<Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ ProgressBar:horizontal">
|
||||||
<Setter Property="MinWidth" Value="0"/>
|
<Setter Property="MinWidth" Value="0" />
|
||||||
<Setter Property="Width" Value="100"/>
|
<Setter Property="Width" Value="100" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Padding="20">
|
<Border Padding="20">
|
||||||
<!-- Add Controls for Previewer Here -->
|
<Border
|
||||||
|
Width="100"
|
||||||
|
Height="100"
|
||||||
|
Classes="Section" />
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
|
|
||||||
<Style Selector="Border.Section">
|
<Style Selector="Border.Section">
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource PrimaryForeground}"/>
|
<Setter Property="BorderBrush" Value="{StaticResource PrimaryForeground}" />
|
||||||
<Setter Property="BorderThickness" Value="1"/>
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="BoxShadow" Value="0 0 1 1 Black"/>
|
<Setter Property="BoxShadow" Value="0 0 1 1 Black" />
|
||||||
<Setter Property="CornerRadius" Value="1"/>
|
<Setter Property="CornerRadius" Value="1" />
|
||||||
</Style>
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Padding="20">
|
<Border Padding="20">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Button Classes="Primary" Content="Primary Button" />
|
<Button Classes="Primary" Content="Primary Button" />
|
||||||
<Button IsEnabled="False" Classes="Primary" Content="Primary Button" />
|
<Button
|
||||||
</StackPanel> <!-- Add Controls for Previewer Here -->
|
Classes="Primary"
|
||||||
|
Content="Primary Button"
|
||||||
|
IsEnabled="False" />
|
||||||
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
|
|
||||||
<Style Selector="Button.Primary">
|
<Style Selector="Button.Primary">
|
||||||
<Setter Property="BorderThickness" Value="1"/>
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource SecondaryDarkestBrush}"/>
|
<Setter Property="BorderBrush" Value="{StaticResource SecondaryDarkestBrush}" />
|
||||||
<Setter Property="Padding" Value="8 12 8 12"/>
|
<Setter Property="Padding" Value="8 12 8 12" />
|
||||||
<Setter Property="CornerRadius" Value="4"/>
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
<Setter Property="Margin" Value="2"/>
|
<Setter Property="Margin" Value="2" />
|
||||||
<Setter Property="Background" Value="{StaticResource SecondaryDark}"/>
|
<Setter Property="Background" Value="{StaticResource SecondaryDark}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource SecondaryLightest}"/>
|
<Setter Property="Foreground" Value="{StaticResource SecondaryLightest}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.Primary:disabled /template/ ContentPresenter">
|
<Style Selector="Button.Primary:disabled /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{StaticResource SecondaryDarkest}"/>
|
<Setter Property="Background" Value="{StaticResource SecondaryDarkest}" />
|
||||||
<Setter Property="Foreground" Value="Gray"/>
|
<Setter Property="Foreground" Value="Gray" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.Primary:pointerover /template/ ContentPresenter">
|
<Style Selector="Button.Primary:pointerover /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{StaticResource SecondaryLightest}"/>
|
<Setter Property="Background" Value="{StaticResource SecondaryLightest}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource SecondaryDarkest}"/>
|
<Setter Property="Foreground" Value="{StaticResource SecondaryDarkest}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
</Styles>
|
</Styles>
|
|
@ -1,15 +1,12 @@
|
||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Padding="20">
|
<Border Padding="20">
|
||||||
<!-- Add Controls for Previewer Here -->
|
<TextBox Classes="Primary" />
|
||||||
<TextBox Classes="Primary"></TextBox>
|
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
|
|
||||||
<Style Selector="TextBox.Primary">
|
<Style Selector="TextBox.Primary">
|
||||||
<Setter Property="Height" Value="32"/>
|
<Setter Property="Height" Value="32" />
|
||||||
|
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
|
|
||||||
<Style Selector="Window">
|
<Style Selector="Window">
|
||||||
<Setter Property="Background" Value="{StaticResource PrimaryBackground}"></Setter>
|
<Setter Property="Background" Value="{StaticResource PrimaryBackground}"/>
|
||||||
|
<Setter Property="CanResize" Value="False"/>
|
||||||
|
<Setter Property="Width" Value="800"/>
|
||||||
|
<Setter Property="Height" Value="450"/>
|
||||||
</Style>
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|
|
@ -2,119 +2,129 @@
|
||||||
using Plpext.Core.AudioPlayer;
|
using Plpext.Core.AudioPlayer;
|
||||||
using Plpext.Core.Models;
|
using Plpext.Core.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
namespace Plpext.UI.ViewModels
|
namespace Plpext.UI.ViewModels;
|
||||||
|
|
||||||
|
public partial class AudioPlayerViewModel : ViewModelBase, IDisposable
|
||||||
{
|
{
|
||||||
public partial class AudioPlayerViewModel : ViewModelBase, IDisposable
|
private readonly AudioPlayer _audioPlayer = null!;
|
||||||
|
private readonly AudioFile _audioFile = null!;
|
||||||
|
private bool _firstExecution = true;
|
||||||
|
|
||||||
|
public AudioFile AudioFile
|
||||||
{
|
{
|
||||||
private readonly AudioPlayer _audioPlayer = null!;
|
get { return _audioFile; }
|
||||||
private readonly AudioFile _audioFile = null!;
|
}
|
||||||
private bool _firstExecution = true;
|
|
||||||
|
public AudioPlayerViewModel()
|
||||||
public AudioFile AudioFile
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioPlayerViewModel(AudioPlayer audioPlayer, AudioFile audioFile)
|
||||||
|
{
|
||||||
|
_audioPlayer = audioPlayer;
|
||||||
|
_audioFile = audioFile;
|
||||||
|
CurrentDuration = "0:00";
|
||||||
|
Name = _audioFile.Name;
|
||||||
|
TotalDuration = $"{_audioFile.Duration:m\\:ss}";
|
||||||
|
audioPlayer.OnProgressUpdated += OnProgressUpdated;
|
||||||
|
audioPlayer.OnPlaybackStopped += OnPlaybackStopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlaybackStopped(object? sender, PlaybackStoppedEventArgs e)
|
||||||
|
{
|
||||||
|
IsPlaying = false;
|
||||||
|
PlaybackState = PlaybackState.Stopped;
|
||||||
|
CurrentDuration = "0:00";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnProgressUpdated(object? sender, PlaybackProgress e)
|
||||||
|
{
|
||||||
|
Progress = e.ProgressPercentage;
|
||||||
|
CurrentDuration = $"{e.CurrentPosition:m\\:ss}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isSelected;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isPlaying;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _totalDuration = null!;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _currentDuration = null!;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _name = null!;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private PlaybackState _playbackState = PlaybackState.Stopped;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private double _progress;
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Play()
|
||||||
|
{
|
||||||
|
if (IsPlaying)
|
||||||
{
|
{
|
||||||
get { return _audioFile; }
|
if (PlaybackState == PlaybackState.Playing)
|
||||||
}
|
|
||||||
|
|
||||||
public AudioPlayerViewModel()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AudioPlayerViewModel(AudioPlayer audioPlayer, AudioFile audioFile)
|
|
||||||
{
|
|
||||||
_audioPlayer = audioPlayer;
|
|
||||||
_audioFile = audioFile;
|
|
||||||
CurrentDuration = "0:00";
|
|
||||||
Name = _audioFile.Name;
|
|
||||||
TotalDuration = $"{_audioFile.Duration:m\\:ss}";
|
|
||||||
audioPlayer.OnProgressUpdated += OnProgressUpdated;
|
|
||||||
audioPlayer.OnPlaybackStopped += OnPlaybackStopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlaybackStopped(object? sender, PlaybackStoppedEventArgs e)
|
|
||||||
{
|
|
||||||
IsPlaying = false;
|
|
||||||
PlaybackState = PlaybackState.Stopped;
|
|
||||||
CurrentDuration = "0:00";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnProgressUpdated(object? sender, PlaybackProgress e)
|
|
||||||
{
|
|
||||||
Progress = e.ProgressPercentage;
|
|
||||||
CurrentDuration = $"{e.CurrentPosition:m\\:ss}";
|
|
||||||
}
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool _isSelected;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool _isPlaying;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string _totalDuration = null!;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string _currentDuration = null!;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string _name = null!;
|
|
||||||
|
|
||||||
[ObservableProperty] private PlaybackState _playbackState = PlaybackState.Stopped;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private double _progress;
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private async Task Play()
|
|
||||||
{
|
|
||||||
if (IsPlaying)
|
|
||||||
{
|
{
|
||||||
if (PlaybackState == PlaybackState.Playing)
|
_audioPlayer.Pause();
|
||||||
{
|
PlaybackState = PlaybackState.Paused;
|
||||||
_audioPlayer.Pause();
|
}
|
||||||
PlaybackState = PlaybackState.Paused;
|
else if (PlaybackState == PlaybackState.Paused)
|
||||||
}
|
{
|
||||||
else if (PlaybackState == PlaybackState.Paused)
|
_audioPlayer.Resume();
|
||||||
{
|
PlaybackState = PlaybackState.Playing;
|
||||||
_audioPlayer.Resume();
|
|
||||||
PlaybackState = PlaybackState.Playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IsPlaying = true;
|
return;
|
||||||
if (_firstExecution)
|
}
|
||||||
|
|
||||||
|
IsPlaying = true;
|
||||||
|
if (_firstExecution)
|
||||||
|
{
|
||||||
|
_firstExecution = false;
|
||||||
|
await _audioPlayer.InitAudioPlayerAsync(_audioFile, true, default);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await _audioPlayer.Start();
|
||||||
|
PlaybackState = PlaybackState.Playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private Task Stop()
|
||||||
|
{
|
||||||
|
IsPlaying = false;
|
||||||
|
_audioPlayer.Stop();
|
||||||
|
PlaybackState = PlaybackState.Stopped;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
{
|
{
|
||||||
_firstExecution = false;
|
_audioPlayer.OnPlaybackStopped -= OnPlaybackStopped;
|
||||||
await _audioPlayer.InitAudioPlayerAsync(_audioFile, true, default);
|
_audioPlayer.OnProgressUpdated -= OnProgressUpdated;
|
||||||
|
_audioPlayer.Dispose();
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
disposedValue = true;
|
||||||
await _audioPlayer.Start();
|
|
||||||
PlaybackState = PlaybackState.Playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private Task Stop()
|
|
||||||
{
|
|
||||||
IsPlaying = false;
|
|
||||||
_audioPlayer.Stop();
|
|
||||||
PlaybackState = PlaybackState.Stopped;
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_audioPlayer.OnPlaybackStopped -= OnPlaybackStopped;
|
|
||||||
_audioPlayer.OnProgressUpdated -= OnProgressUpdated;
|
|
||||||
_audioPlayer.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,9 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public MainWindowViewModel(IPlatformStorageService platformStorageService, IFileLoaderService fileLoaderService,
|
public MainWindowViewModel(
|
||||||
|
IPlatformStorageService platformStorageService,
|
||||||
|
IFileLoaderService fileLoaderService,
|
||||||
IConvertService convertService)
|
IConvertService convertService)
|
||||||
{
|
{
|
||||||
_fileLoaderService = fileLoaderService;
|
_fileLoaderService = fileLoaderService;
|
||||||
|
@ -26,30 +28,43 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||||
_convertService = convertService;
|
_convertService = convertService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(LoadFileCommand))]
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(LoadFileCommand))]
|
||||||
private string _originPath = null!;
|
private string _originPath = null!;
|
||||||
|
|
||||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand),nameof(ConvertSelectedFilesCommand))]
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand), nameof(ConvertSelectedFilesCommand))]
|
||||||
private string _targetPath = null!;
|
private string _targetPath = null!;
|
||||||
|
|
||||||
[ObservableProperty] private int _totalFilesToExtract;
|
[ObservableProperty]
|
||||||
|
private int _totalFilesToExtract;
|
||||||
|
|
||||||
[ObservableProperty] private int _filesReady;
|
[ObservableProperty]
|
||||||
|
private int _filesReady;
|
||||||
|
|
||||||
[ObservableProperty] private string _progressBarText = null!;
|
[ObservableProperty]
|
||||||
|
private string _progressBarText = null!;
|
||||||
|
|
||||||
[ObservableProperty] private double _progressBarValue;
|
[ObservableProperty]
|
||||||
|
private double _progressBarValue;
|
||||||
|
|
||||||
[ObservableProperty] private bool _isProgressBarIndeterminate;
|
[ObservableProperty]
|
||||||
|
private bool _isProgressBarIndeterminate;
|
||||||
|
|
||||||
[ObservableProperty] private string _progressBarDetails = null!;
|
[ObservableProperty]
|
||||||
|
private string _progressBarDetails = null!;
|
||||||
|
|
||||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand), nameof(ConvertSelectedFilesCommand), nameof(LoadFileCommand))]
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand), nameof(ConvertSelectedFilesCommand), nameof(LoadFileCommand))]
|
||||||
private bool _showProgressBar;
|
private bool _showProgressBar;
|
||||||
|
|
||||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand),nameof(ConvertSelectedFilesCommand))]
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand), nameof(ConvertSelectedFilesCommand))]
|
||||||
private ObservableCollection<AudioPlayerViewModel> _audioFiles = new();
|
private ObservableCollection<AudioPlayerViewModel> _audioFiles = new();
|
||||||
|
|
||||||
|
private bool CanLoadFile() => !string.IsNullOrEmpty(OriginPath) && !ShowProgressBar;
|
||||||
|
private bool CanConvertAllFiles() => !string.IsNullOrEmpty(TargetPath) && AudioFiles.Any() && !ShowProgressBar;
|
||||||
|
private bool CanConvertSelectFiles() => CanConvertAllFiles();
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanLoadFile))]
|
[RelayCommand(CanExecute = nameof(CanLoadFile))]
|
||||||
private async Task LoadFile()
|
private async Task LoadFile()
|
||||||
|
@ -79,9 +94,6 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||||
await Dispatcher.UIThread.InvokeAsync(() => ShowProgressBar = false);
|
await Dispatcher.UIThread.InvokeAsync(() => ShowProgressBar = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanLoadFile() => !string.IsNullOrEmpty(OriginPath) && !ShowProgressBar;
|
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task SelectOriginPath()
|
private async Task SelectOriginPath()
|
||||||
{
|
{
|
||||||
|
@ -108,10 +120,6 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||||
await Dispatcher.UIThread.InvokeAsync(() => ShowProgressBar = false);
|
await Dispatcher.UIThread.InvokeAsync(() => ShowProgressBar = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanConvertAllFiles() => !string.IsNullOrEmpty(TargetPath) && AudioFiles.Any() && !ShowProgressBar;
|
|
||||||
private bool CanConvertSelectFiles() => CanConvertAllFiles();
|
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanConvertSelectFiles))]
|
[RelayCommand(CanExecute = nameof(CanConvertSelectFiles))]
|
||||||
private async Task ConvertSelectedFiles()
|
private async Task ConvertSelectedFiles()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
namespace Plpext.UI.ViewModels
|
namespace Plpext.UI.ViewModels;
|
||||||
|
|
||||||
|
public class ViewModelBase : ObservableObject
|
||||||
{
|
{
|
||||||
public class ViewModelBase : ObservableObject
|
}
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,8 +9,6 @@
|
||||||
Title="Plpext"
|
Title="Plpext"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
Width="800"
|
|
||||||
Height="450"
|
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Icon="/Assets/plpext.png"
|
Icon="/Assets/plpext.png"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
@ -24,19 +22,17 @@
|
||||||
</Window.Styles>
|
</Window.Styles>
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<!--
|
|
||||||
This only sets the DataContext for the previewer in an IDE,
|
|
||||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs)
|
|
||||||
-->
|
|
||||||
<vm:MainWindowViewModel />
|
<vm:MainWindowViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
Margin="16, 8, 16, 8"
|
Margin="16,8,16,8"
|
||||||
ColumnDefinitions="300,32,*"
|
ColumnDefinitions="300,32,*"
|
||||||
RowDefinitions="Auto,Auto,*">
|
RowDefinitions="Auto,Auto,*">
|
||||||
<Border Grid.Row="0"
|
<Border
|
||||||
Grid.Column="0" Classes="Section">
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Classes="Section">
|
||||||
|
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<Grid
|
<Grid
|
||||||
|
@ -44,10 +40,11 @@
|
||||||
ColumnDefinitions="*"
|
ColumnDefinitions="*"
|
||||||
RowDefinitions="*, Auto, Auto">
|
RowDefinitions="*, Auto, Auto">
|
||||||
<Label Grid.Row="0" Content="Select your .plp pack file:" />
|
<Label Grid.Row="0" Content="Select your .plp pack file:" />
|
||||||
<StackPanel Height="36"
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Orientation="Horizontal"
|
Height="36"
|
||||||
Spacing="2">
|
Orientation="Horizontal"
|
||||||
|
Spacing="2">
|
||||||
<TextBox
|
<TextBox
|
||||||
Width="248"
|
Width="248"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
|
@ -59,10 +56,10 @@
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button
|
<Button
|
||||||
Height="38"
|
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
Height="38"
|
||||||
|
Margin="0,4,0,0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Margin="0 4 0 0"
|
|
||||||
Classes="Primary"
|
Classes="Primary"
|
||||||
Command="{Binding LoadFileCommand}">
|
Command="{Binding LoadFileCommand}">
|
||||||
<TextBlock VerticalAlignment="Center">Load</TextBlock>
|
<TextBlock VerticalAlignment="Center">Load</TextBlock>
|
||||||
|
@ -78,10 +75,10 @@
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
Content="Select output folder:" />
|
Content="Select output folder:" />
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Height="36"
|
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
|
Height="36"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="2">
|
Spacing="2">
|
||||||
<TextBox
|
<TextBox
|
||||||
|
@ -94,19 +91,19 @@
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button
|
<Button
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Height="38"
|
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
|
Height="38"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
Classes="Primary"
|
Classes="Primary"
|
||||||
Command="{Binding ConvertAllFilesCommand}">
|
Command="{Binding ConvertAllFilesCommand}">
|
||||||
<TextBlock VerticalAlignment="Center">Extract All</TextBlock>
|
<TextBlock VerticalAlignment="Center">Extract All</TextBlock>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
Height="38"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
|
Height="38"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
Classes="Primary"
|
Classes="Primary"
|
||||||
Command="{Binding ConvertSelectedFilesCommand}">
|
Command="{Binding ConvertSelectedFilesCommand}">
|
||||||
<TextBlock VerticalAlignment="Center">Extract Selected</TextBlock>
|
<TextBlock VerticalAlignment="Center">Extract Selected</TextBlock>
|
||||||
|
@ -117,12 +114,21 @@
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
<Grid Grid.Row="2" Grid.Column="0">
|
<Grid Grid.Row="2" Grid.Column="0">
|
||||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Height="60" IsVisible="{Binding ShowProgressBar}">
|
<StackPanel
|
||||||
<Label Content="{Binding ProgressBarText}"/>
|
Height="60"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding ShowProgressBar}"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<Label Content="{Binding ProgressBarText}" />
|
||||||
<Border Classes="Section">
|
<Border Classes="Section">
|
||||||
<ProgressBar Width="298" Orientation="Horizontal" Height="50" IsIndeterminate="{Binding IsProgressBarIndeterminate}" Value="{Binding ProgressBarValue}" />
|
<ProgressBar
|
||||||
|
Width="298"
|
||||||
|
Height="50"
|
||||||
|
IsIndeterminate="{Binding IsProgressBarIndeterminate}"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Value="{Binding ProgressBarValue}" />
|
||||||
</Border>
|
</Border>
|
||||||
<Label Content="{Binding ProgressBarDetails}" HorizontalAlignment="Right"/>
|
<Label HorizontalAlignment="Right" Content="{Binding ProgressBarDetails}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
@ -136,23 +142,24 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
MaxHeight="410"
|
MaxHeight="410"
|
||||||
CanUserSortColumns="False"
|
|
||||||
CanUserResizeColumns="False"
|
|
||||||
CanUserReorderColumns="False"
|
|
||||||
AreRowDetailsFrozen="True"
|
AreRowDetailsFrozen="True"
|
||||||
HeadersVisibility="None"
|
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
VerticalScrollBarVisibility="Visible"
|
CanUserReorderColumns="False"
|
||||||
|
CanUserResizeColumns="False"
|
||||||
|
CanUserSortColumns="False"
|
||||||
|
HeadersVisibility="None"
|
||||||
IsReadOnly="False"
|
IsReadOnly="False"
|
||||||
|
ItemsSource="{Binding AudioFiles}"
|
||||||
SelectionMode="Extended"
|
SelectionMode="Extended"
|
||||||
ItemsSource="{Binding AudioFiles}">
|
VerticalScrollBarVisibility="Visible">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTemplateColumn Width="36">
|
<DataGridTemplateColumn Width="36">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}"
|
<CheckBox
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center" />
|
VerticalAlignment="Center"
|
||||||
|
IsChecked="{Binding IsSelected, Mode=TwoWay}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
@ -167,14 +174,14 @@
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate DataType="vm:AudioPlayerViewModel">
|
<DataTemplate DataType="vm:AudioPlayerViewModel">
|
||||||
<c:AudioPlayerControl
|
<c:AudioPlayerControl
|
||||||
Margin="0 0 16 0"
|
Margin="0,0,16,0"
|
||||||
CurrentDuration="{Binding CurrentDuration}"
|
CurrentDuration="{Binding CurrentDuration}"
|
||||||
TotalDuration="{Binding TotalDuration}"
|
|
||||||
PlayCommand="{Binding PlayCommand}"
|
|
||||||
StopCommand="{Binding StopCommand}"
|
|
||||||
IsPlaying="{Binding IsPlaying}"
|
IsPlaying="{Binding IsPlaying}"
|
||||||
|
PlayCommand="{Binding PlayCommand}"
|
||||||
PlaybackState="{Binding PlaybackState}"
|
PlaybackState="{Binding PlaybackState}"
|
||||||
Progress="{Binding Progress}" />
|
Progress="{Binding Progress}"
|
||||||
|
StopCommand="{Binding StopCommand}"
|
||||||
|
TotalDuration="{Binding TotalDuration}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"version": "1.0.0",
|
|
||||||
"executable": "Plpext.exe",
|
|
||||||
"iconFile": "Assets/plpext.png",
|
|
||||||
"splashImage": "Assets/plpext.png"
|
|
||||||
}
|
|
|
@ -9,34 +9,24 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plpext.Core", "..\Plpext.Co
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Debug|x64 = Debug|x64
|
Debug|x64 = Debug|x64
|
||||||
Debug|x86 = Debug|x86
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
Release|x64 = Release|x64
|
Release|x64 = Release|x64
|
||||||
Release|x86 = Release|x86
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x64.ActiveCfg = Debug|x64
|
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x64.Build.0 = Debug|x64
|
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x64.Build.0 = Debug|x64
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x86.ActiveCfg = Debug|x86
|
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x86.Build.0 = Debug|x86
|
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x86.Build.0 = Debug|x86
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x64.ActiveCfg = Release|x64
|
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x64.ActiveCfg = Release|x64
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x64.Build.0 = Release|x64
|
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x64.Build.0 = Release|x64
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x86.ActiveCfg = Release|x86
|
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x86.ActiveCfg = Release|x86
|
||||||
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x86.Build.0 = Release|x86
|
{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x86.Build.0 = Release|x86
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x64.ActiveCfg = Debug|x64
|
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x64.Build.0 = Debug|x64
|
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x64.Build.0 = Debug|x64
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x86.ActiveCfg = Debug|x86
|
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x86.Build.0 = Debug|x86
|
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x86.Build.0 = Debug|x86
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x64.ActiveCfg = Release|x64
|
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x64.ActiveCfg = Release|x64
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x64.Build.0 = Release|x64
|
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x64.Build.0 = Release|x64
|
||||||
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x86.ActiveCfg = Release|x86
|
{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
|
Loading…
Reference in a new issue