Updated SongPictureInfo use to async copy of picture data stream. Update other classes accordingly.

This commit is contained in:
2025-03-02 23:28:28 -05:00
parent 1a9c1a5478
commit 0675131195
11 changed files with 178 additions and 149 deletions

View File

@@ -30,11 +30,11 @@ public class AudioImageMemoryCache(IAudioImageExtractor audioImageExtractor) : M
return null; return null;
} }
protected override ValueTask<SongPictureInfo?> FetchAsync(Song key, CancellationToken cancellationToken) protected override async ValueTask<SongPictureInfo?> FetchAsync(Song key, CancellationToken cancellationToken)
{ {
SongPictureInfo? songPictureInfo = audioImageExtractor.ExtractImage(key.FileName); SongPictureInfo? songPictureInfo = await audioImageExtractor.ExtractImageAsync(key.FileName, cancellationToken);
return ValueTask.FromResult(songPictureInfo); return songPictureInfo;
} }
protected override long GetEntrySize(SongPictureInfo entry) protected override long GetEntrySize(SongPictureInfo entry)

View File

@@ -46,20 +46,20 @@ public class AudioImageExtractor : IAudioImageExtractor
]; ];
} }
public SongPictureInfo? ExtractImage(string path) public async Task<SongPictureInfo?> ExtractImageAsync(string path, CancellationToken cancellationToken)
{ {
SongTagInfo songTagInfo = _tagResolver.GetSongTagInfo(path); SongTagInfo songTagInfo = _tagResolver.GetSongTagInfo(path);
return ExtractImage(path, songTagInfo); return await ExtractImageAsync(path, songTagInfo, cancellationToken);
} }
public SongPictureInfo? ExtractImage(string path, SongTagInfo songTagInfo) public async Task<SongPictureInfo?> ExtractImageAsync(string path, SongTagInfo songTagInfo, CancellationToken cancellationToken)
{ {
if (songTagInfo.FrontCover != null) if (songTagInfo.FrontCover != null)
{ {
using MemoryStream memoryStream = new(songTagInfo.FrontCover.Data); using MemoryStream memoryStream = new(songTagInfo.FrontCover.Data);
return SongPictureInfo.FromStream(memoryStream); return await SongPictureInfo.FromStreamAsync(memoryStream, cancellationToken);
} }
else else
{ {
@@ -68,7 +68,7 @@ public class AudioImageExtractor : IAudioImageExtractor
if (string.IsNullOrWhiteSpace(imagePath)) if (string.IsNullOrWhiteSpace(imagePath))
return null; return null;
return SongPictureInfo.FromFile(path); return await SongPictureInfo.FromFileAsync(path, cancellationToken);
} }
} }

View File

@@ -4,6 +4,6 @@ namespace Harmonia.Core.Imaging;
public interface IAudioImageExtractor public interface IAudioImageExtractor
{ {
SongPictureInfo? ExtractImage(string path); Task<SongPictureInfo?> ExtractImageAsync(string path, CancellationToken cancellationToken);
SongPictureInfo? ExtractImage(string path, SongTagInfo songTagInfo); Task<SongPictureInfo?> ExtractImageAsync(string path, SongTagInfo songTagInfo, CancellationToken cancellationToken);
} }

View File

@@ -2,23 +2,23 @@
namespace Harmonia.Core.Imaging; namespace Harmonia.Core.Imaging;
public class SongPictureInfo : IDisposable public class SongPictureInfo
{ {
public required Stream Stream { get; init; } public required byte[] Data { get; init; }
public string? ImageHash { get; init; } public string? ImageHash { get; private set; }
public required string ImageName { get; init; } public required string ImageName { get; init; }
public long Size { get; init; } public long Size => Data.Length;
private SongPictureInfo() private SongPictureInfo()
{ {
} }
public static SongPictureInfo FromStream(Stream stream) public static async Task<SongPictureInfo> FromStreamAsync(Stream stream, CancellationToken cancellationToken)
{ {
string imageHash = ComputeImageHash(stream); string imageHash = ComputeImageHash(stream);
return GetSongPictureInfo(stream, "Embedded", imageHash); return await GetSongPictureInfoAsync(stream, "Embedded", imageHash, cancellationToken);
} }
private static string ComputeImageHash(Stream stream) private static string ComputeImageHash(Stream stream)
@@ -29,31 +29,30 @@ public class SongPictureInfo : IDisposable
return Convert.ToHexStringLower(hash); return Convert.ToHexStringLower(hash);
} }
public static SongPictureInfo FromFile(string fileName) public static async Task<SongPictureInfo> FromFileAsync(string fileName, CancellationToken cancellationToken)
{ {
using FileStream fileStream = File.OpenRead(fileName); using FileStream fileStream = File.OpenRead(fileName);
return GetSongPictureInfo(fileStream, fileName); return await GetSongPictureInfoAsync(fileStream, fileName, cancellationToken: cancellationToken);
} }
private static SongPictureInfo GetSongPictureInfo(Stream stream, string imageName, string? imageHash = null) private static async Task<SongPictureInfo> GetSongPictureInfoAsync(Stream stream, string imageName, string? imageHash = null, CancellationToken cancellationToken = default)
{
return new()
{
Data = await GetImageDataAsync(stream, cancellationToken),
ImageHash = imageHash,
ImageName = imageName
};
}
public static async Task<byte[]> GetImageDataAsync(Stream stream, CancellationToken cancellationToken)
{ {
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
SongPictureInfo songPictureInfo = new() using MemoryStream memoryStream = new();
{ await stream.CopyToAsync(memoryStream, cancellationToken);
Stream = stream,
ImageHash = imageHash,
ImageName = imageName,
Size = stream.Length
};
return songPictureInfo; return memoryStream.ToArray();
}
public void Dispose()
{
Stream.Dispose();
GC.SuppressFinalize(this);
} }
} }

View File

@@ -6,7 +6,7 @@ namespace Harmonia.Core.Library;
public interface IAudioLibrary public interface IAudioLibrary
{ {
void AddLocation(string path); ValueTask AddLocationAsync(string path, CancellationToken cancellationToken);
void RemoveLocation(string path); void RemoveLocation(string path);
void RemoveLocations(string[] paths); void RemoveLocations(string[] paths);
void Scan(string path); void Scan(string path);
@@ -25,12 +25,12 @@ public abstract class AudioLibrary : IAudioLibrary
public event EventHandler<EventArgs>? ScanStarted; public event EventHandler<EventArgs>? ScanStarted;
public event EventHandler<EventArgs>? ScanFinished; public event EventHandler<EventArgs>? ScanFinished;
protected abstract bool IncludeLocation(string path); protected abstract ValueTask<bool> IncludeLocationAsync(string path, CancellationToken cancellationToken);
protected abstract void ScanLocation(string path); protected abstract ValueTask ScanLocationAsync(string path, CancellationToken cancellationToken);
public void AddLocation(string path) public async ValueTask AddLocationAsync(string path, CancellationToken cancellationToken)
{ {
if (IncludeLocation(path) == false) if (await IncludeLocationAsync(path, cancellationToken) == false)
return; return;
LocationAdded?.Invoke(this, new EventArgs()); LocationAdded?.Invoke(this, new EventArgs());
@@ -69,13 +69,13 @@ public abstract class AudioLibrary : IAudioLibrary
public class AudioDatabaseLibrary(IAudioFileScanner audioFileScanner, AudioLibraryContext context) : AudioLibrary public class AudioDatabaseLibrary(IAudioFileScanner audioFileScanner, AudioLibraryContext context) : AudioLibrary
{ {
protected override bool IncludeLocation(string path) protected override async ValueTask<bool> IncludeLocationAsync(string path, CancellationToken cancellationToken)
{ {
if (CanAddLocation(path) == false) if (CanAddLocation(path) == false)
return false; return false;
Location location = GetLocation(path) ?? CreateLocation(path); Location location = GetLocation(path) ?? CreateLocation(path);
ScanLocation(location); await ScanLocationAsync(location, cancellationToken);
return true; return true;
} }
@@ -104,19 +104,19 @@ public class AudioDatabaseLibrary(IAudioFileScanner audioFileScanner, AudioLibra
return context.Locations.FirstOrDefault(location => location.Name == path); return context.Locations.FirstOrDefault(location => location.Name == path);
} }
protected override void ScanLocation(string path) protected override async ValueTask ScanLocationAsync(string path, CancellationToken cancellationToken)
{ {
Location? location = GetLocation(path); Location? location = GetLocation(path);
if (location == null) if (location == null)
return; return;
ScanLocation(location); await ScanLocationAsync(location, cancellationToken);
} }
private void ScanLocation(Location location) private async ValueTask ScanLocationAsync(Location location, CancellationToken cancellationToken)
{ {
SongModel[] songModels = audioFileScanner.GetSongsFromPath(location.Name); SongModel[] songModels = await audioFileScanner.GetSongsFromPathAsync(location.Name, cancellationToken);
AddFolders(location, songModels); AddFolders(location, songModels);
AddSongs(songModels); AddSongs(songModels);

View File

@@ -7,7 +7,7 @@ namespace Harmonia.Core.Scanner;
public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver, IAudioImageExtractor audioImageExtractor) : IAudioFileScanner public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver, IAudioImageExtractor audioImageExtractor) : IAudioFileScanner
{ {
public Song[] GetSongs(string[] fileNames) public async Task<Song[]> GetSongsAsync(string[] fileNames, CancellationToken cancellationToken)
{ {
List<Song> songs = []; List<Song> songs = [];
@@ -16,7 +16,7 @@ public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver
if (string.IsNullOrWhiteSpace(fileName) || File.Exists(fileName) == false) if (string.IsNullOrWhiteSpace(fileName) || File.Exists(fileName) == false)
continue; continue;
Song? song = GetSong(fileName); Song? song = await GetSongAsync(fileName, cancellationToken);
if (song == null) if (song == null)
continue; continue;
@@ -27,11 +27,12 @@ public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver
return [.. songs]; return [.. songs];
} }
private Song GetSong(string fileName) private async Task<Song> GetSongAsync(string fileName, CancellationToken cancellationToken)
{ {
FileInfo fileInfo = new(fileName); FileInfo fileInfo = new(fileName);
SongTagInfo songTagInfo = tagResolver.GetSongTagInfo(fileName); SongTagInfo songTagInfo = tagResolver.GetSongTagInfo(fileName);
using SongPictureInfo? songPictureInfo = audioImageExtractor.ExtractImage(fileName, songTagInfo); //using SongPictureInfo? songPictureInfo = audioImageExtractor.ExtractImage(fileName, songTagInfo);
SongPictureInfo? songPictureInfo = await audioImageExtractor.ExtractImageAsync(fileName, songTagInfo, cancellationToken);
Song song = new() Song song = new()
{ {
@@ -56,12 +57,12 @@ public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver
return song; return song;
} }
public Song[] GetSongsFromPath(string path) public async Task<Song[]> GetSongsFromPathAsync(string path, CancellationToken cancellationToken)
{ {
FileInfo[] fileInfoList = GetAllFilesFromDirectory(path); FileInfo[] fileInfoList = GetAllFilesFromDirectory(path);
string[] fileNames = [.. fileInfoList.Select(x => x.FullName)]; string[] fileNames = [.. fileInfoList.Select(x => x.FullName)];
return GetSongs(fileNames); return await GetSongsAsync(fileNames, cancellationToken);
} }
private FileInfo[] GetAllFilesFromDirectory(string directoryName) private FileInfo[] GetAllFilesFromDirectory(string directoryName)

View File

@@ -4,6 +4,6 @@ namespace Harmonia.Core.Scanner;
public interface IAudioFileScanner public interface IAudioFileScanner
{ {
Song[] GetSongs(string[] fileNames); Task<Song[]> GetSongsAsync(string[] fileNames, CancellationToken cancellationToken);
Song[] GetSongsFromPath(string path); Task<Song[]> GetSongsFromPathAsync(string path, CancellationToken cancellationToken);
} }

View File

@@ -1,6 +1,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using Harmonia.Core.Caching; using Harmonia.Core.Caching;
using Harmonia.Core.Imaging; using Harmonia.Core.Imaging;
using Harmonia.Core.Models; using Harmonia.Core.Models;
@@ -8,9 +9,11 @@ using Harmonia.Core.Player;
using Harmonia.Core.Playlists; using Harmonia.Core.Playlists;
using Harmonia.Core.Scanner; using Harmonia.Core.Scanner;
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input;
namespace Harmonia.UI.ViewModels; namespace Harmonia.UI.ViewModels;
@@ -33,6 +36,7 @@ public partial class PlaybackBarViewModel : ViewModelBase
{ {
_song = value; _song = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(FormattedSongInfo));
CurrentPosition = 0; // What if player is being loaded and returning to save state? CurrentPosition = 0; // What if player is being loaded and returning to save state?
Position = 0; Position = 0;
@@ -54,6 +58,10 @@ public partial class PlaybackBarViewModel : ViewModelBase
} }
} }
public string FormattedSongInfo => Song != null
? $"{Song.FileType} - {Song.BitRate} kbps - {Song.SampleRate} Hz"
: string.Empty;
private double _currentPosition; private double _currentPosition;
public double CurrentPosition public double CurrentPosition
{ {
@@ -123,7 +131,11 @@ public partial class PlaybackBarViewModel : ViewModelBase
} }
} }
public string Greeting => "Welcome to Harmonia!"; public ICommand PlaySongCommand => new RelayCommand(Play);
public ICommand PauseSongCommand => new RelayCommand(Pause);
public ICommand StopSongCommand => new RelayCommand(Stop);
public ICommand PreviousSongCommand => new RelayCommand(Previous);
public ICommand NextSongCommand => new RelayCommand(Next);
public PlaybackBarViewModel(IAudioPlayer audioPlayer, IAudioImageCache audioImageCache, IPlaylistRepository playlistRepository, IAudioFileScanner audioFileScanner) public PlaybackBarViewModel(IAudioPlayer audioPlayer, IAudioImageCache audioImageCache, IPlaylistRepository playlistRepository, IAudioFileScanner audioFileScanner)
{ {
@@ -133,6 +145,27 @@ public partial class PlaybackBarViewModel : ViewModelBase
_audioImageCache = audioImageCache; _audioImageCache = audioImageCache;
_timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Default, TickTock); _timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Default, TickTock);
PlayDemoSong(playlistRepository, audioFileScanner);
}
private async Task PlayDemoSong(IPlaylistRepository playlistRepository, IAudioFileScanner audioFileScanner, CancellationToken cancellationToken = default)
{
if (playlistRepository.Get().Count == 0)
{
playlistRepository.AddPlaylist();
Playlist playlist = playlistRepository.Get().First();
//string songPath = @"D:\Music\Game Music\Bobby Prince\Doom II";
//string songPath = @"D:\Music\Anime Music\HimeHina";
string songPath = @"D:\Music\K-Pop";
Song[] songs = await audioFileScanner.GetSongsFromPathAsync(songPath, cancellationToken);
playlist.AddSongs(songs);
}
await _audioPlayer.LoadAsync(playlistRepository.Get().First().Songs[0], PlaybackMode.LoadAndPlay);
} }
private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e) private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
@@ -163,7 +196,11 @@ public partial class PlaybackBarViewModel : ViewModelBase
private void SetSongImageSource(SongPictureInfo songPictureInfo) private void SetSongImageSource(SongPictureInfo songPictureInfo)
{ {
SongImageSource = new(songPictureInfo.Stream); if (songPictureInfo.Data.Length == 0)
return;
using MemoryStream stream = new(songPictureInfo.Data);
SongImageSource = new(stream);
} }
private void TickTock(object? sender, object e) private void TickTock(object? sender, object e)

View File

@@ -14,9 +14,12 @@
<vm:MainViewModel /> <vm:MainViewModel />
</Design.DataContext> </Design.DataContext>
<StackPanel> <Grid>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/> <Grid.RowDefinitions>
<views:PlaybackBar></views:PlaybackBar> <RowDefinition Height="*"></RowDefinition>
</StackPanel> <RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<views:PlaybackBar Grid.Row="1"></views:PlaybackBar>
</Grid>
</UserControl> </UserControl>

View File

@@ -14,80 +14,94 @@
<converter:NullVisibilityConverter x:Key="NullVisibility" /> <converter:NullVisibilityConverter x:Key="NullVisibility" />
<converter:SongTitleConverter x:Key="SongTitle" /> <converter:SongTitleConverter x:Key="SongTitle" />
</UserControl.Resources> </UserControl.Resources>
<Grid> <UserControl.Styles>
<Grid.ColumnDefinitions> <!-- Flat Button -->
<ColumnDefinition></ColumnDefinition> <Style Selector="Button.Flat">
<ColumnDefinition></ColumnDefinition> <Setter Property="Padding" Value="10"/>
<ColumnDefinition></ColumnDefinition> <Setter Property="Margin" Value="8 0 8 0"/>
</Grid.ColumnDefinitions> <Setter Property="Background" Value="Transparent"/>
<Grid.RowDefinitions> <Setter Property="BorderThickness" Value="0"/>
<RowDefinition Height="Auto"></RowDefinition> </Style>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions> <!-- Flat Button Path Icon -->
<Style Selector="PathIcon.FlatButtonIcon">
<!-- Slider --> <Setter Property="Padding" Value="10"/>
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="0 0 0 10"> <Setter Property="Width" Value="18"/>
<Setter Property="Height" Value="18"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<!-- Flat Button Path Icon (Large) -->
<Style Selector="PathIcon.FlatButtonIcon.Large">
<Setter Property="Width" Value="36"/>
<Setter Property="Height" Value="36"/>
</Style>
</UserControl.Styles>
<Border Background="#1a1a1a" Padding="10">
<Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Column="0" Foreground="#aaaaaa" Text="{Binding Position, Converter={StaticResource SecondsToString}}" FontSize="13" Margin="0 0 6 0" VerticalAlignment="Center"></TextBlock>
<Slider Name="TrackSlider" Loaded="Slider_Loaded" Grid.Column="1" Value="{Binding CurrentPosition, Mode=TwoWay, UpdateSourceTrigger=Explicit}" Minimum="0" Maximum="{Binding MaxPosition, Mode=OneWay}" VerticalAlignment="Center" />
<TextBlock Grid.Column="2" Foreground="#aaaaaa" Text="{Binding MaxPosition, Converter={StaticResource SecondsToString}}" FontSize="13" Margin="6 0 0 0" VerticalAlignment="Center"></TextBlock>
</Grid>
<!-- Song Info --> <!-- Slider -->
<Grid Grid.Row="1" Grid.Column="0" Name="PlayingSongGrid"> <Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="0 0 0 10">
<StackPanel Orientation="Horizontal"> <Grid.ColumnDefinitions>
<Grid Margin="0 0 10 0"> <ColumnDefinition Width="Auto"></ColumnDefinition>
<!--<Image Name="PlayingSongImage" Width="80" Height="80"></Image>--> <ColumnDefinition></ColumnDefinition>
<Image Source="{Binding SongImageSource, Mode=TwoWay}" Width="80" Height="80"></Image> <ColumnDefinition Width="Auto"></ColumnDefinition>
<Canvas Background="#19000000"></Canvas> </Grid.ColumnDefinitions>
</Grid> <Grid.RowDefinitions>
<StackPanel VerticalAlignment="Center"> <RowDefinition Height="Auto"></RowDefinition>
<TextBlock Foreground="#dddddd" FontWeight="SemiBold" FontSize="16" LineHeight="20" Text="{Binding Song, Converter={StaticResource SongTitle}}"></TextBlock> </Grid.RowDefinitions>
<TextBlock Foreground="#aaaaaa" FontSize="14" LineHeight="20" Text="{Binding Song.Artists, Converter={StaticResource ArtistsToString}}"></TextBlock> <TextBlock Grid.Column="0" Foreground="#aaaaaa" Text="{Binding Position, Converter={StaticResource SecondsToString}}" FontSize="13" Margin="0 0 6 0" VerticalAlignment="Center"></TextBlock>
<TextBlock Foreground="#aaaaaa" FontSize="14" LineHeight="20" Text="{Binding Song.Album}"></TextBlock> <Slider Name="TrackSlider" Loaded="Slider_Loaded" Grid.Column="1" Value="{Binding CurrentPosition, Mode=TwoWay, UpdateSourceTrigger=Explicit}" Minimum="0" Maximum="{Binding MaxPosition, Mode=OneWay}" VerticalAlignment="Center" />
<TextBlock Foreground="#777" FontSize="12" IsVisible="{Binding Song, Converter={StaticResource NullVisibility}}" FontWeight="Normal" Opacity="1.0"> <TextBlock Grid.Column="2" Foreground="#aaaaaa" Text="{Binding MaxPosition, Converter={StaticResource SecondsToString}}" FontSize="13" Margin="6 0 0 0" VerticalAlignment="Center"></TextBlock>
<Run Text="{Binding Song.FileType}"></Run> </Grid>
<Run Text=" - "></Run>
<Run Text="{Binding Song.BitRate}"></Run> <!-- Song Info -->
<Run Text=" kbps - "></Run> <Grid Grid.Row="1" Grid.Column="0" Name="PlayingSongGrid">
<Run Text="{Binding Song.SampleRate}"></Run> <StackPanel Orientation="Horizontal">
<Run Text=" Hz"></Run> <Grid Margin="0 0 10 0">
</TextBlock> <!--<Image Name="PlayingSongImage" Width="80" Height="80"></Image>-->
<Image Source="{Binding SongImageSource, Mode=TwoWay}" Width="80" Height="80"></Image>
<Canvas Background="#19000000"></Canvas>
</Grid>
<StackPanel VerticalAlignment="Center">
<TextBlock Foreground="#dddddd" FontWeight="SemiBold" FontSize="16" LineHeight="20" Text="{Binding Song, Converter={StaticResource SongTitle}}"></TextBlock>
<TextBlock Foreground="#aaaaaa" FontSize="14" LineHeight="20" Text="{Binding Song.Artists, Converter={StaticResource ArtistsToString}}"></TextBlock>
<TextBlock Foreground="#aaaaaa" FontSize="14" LineHeight="20" Text="{Binding Song.Album}"></TextBlock>
<TextBlock Foreground="#777" FontSize="12" Text="{Binding FormattedSongInfo}" IsVisible="{Binding Song, Converter={StaticResource NullVisibility}}" FontWeight="Normal" Opacity="1.0">
</TextBlock>
</StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </Grid>
</Grid>
<!-- Action Buttons --> <!-- Action Buttons -->
<Grid Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center"> <Grid Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Button Margin="8 0 8 0" Click="PreviousSongButton_Click" Theme="{StaticResource BorderlessButton}"> <Button Classes="Flat" Command="{Binding PreviousSongCommand}">
<Button.Content> <PathIcon Classes="FlatButtonIcon" Data="{StaticResource SemiIconBackward}"></PathIcon>
<StackPanel Orientation="Horizontal"> </Button>
<PathIcon Data="{StaticResource SemiIconBackward}" Margin="0 0 0 0"></PathIcon> <Button Classes="Flat" Command="{Binding StopSongCommand}">
<TextBlock Text="" FontSize="18"/> <PathIcon Classes="FlatButtonIcon" Data="{StaticResource SemiIconStop}"></PathIcon>
</StackPanel> </Button>
</Button.Content> <Button Classes="Flat" Command="{Binding PlaySongCommand}">
</Button> <PathIcon Classes="FlatButtonIcon Large" Data="{StaticResource SemiIconPlay}"></PathIcon>
<Button Margin="8 0 8 0" Click="StopButton_Click"> </Button>
<TextBlock Text="&#x0042;" FontSize="18"></TextBlock> <Button Classes="Flat" Command="{Binding PauseSongCommand}">
</Button> <PathIcon Classes="FlatButtonIcon" Data="{StaticResource SemiIconPause}"></PathIcon>
<Button Margin="8 0 8 0" Click="PlayButton_Click"> </Button>
<TextBlock Text="&#x0041;" FontSize="36"></TextBlock> <Button Classes="Flat" Command="{Binding NextSongCommand}">
</Button> <PathIcon Classes="FlatButtonIcon" Data="{StaticResource SemiIconFastForward}"></PathIcon>
<Button Margin="8 0 8 0" Click="PauseButton_Click"> </Button>
<TextBlock Text="&#x0043;" FontSize="18"></TextBlock> </StackPanel>
</Button> </Grid>
<Button Margin="8 0 8 0" Click="NextSongButton_Click">
<TextBlock Text="&#x0045;" FontSize="18"></TextBlock>
</Button>
</StackPanel>
</Grid> </Grid>
</Grid> </Border>
</UserControl> </UserControl>

View File

@@ -45,29 +45,4 @@ public partial class PlaybackBar : UserControl
_viewModel.CurrentPosition = slider.Value; _viewModel.CurrentPosition = slider.Value;
_viewModel.IsPositionChangeInProgress = false; _viewModel.IsPositionChangeInProgress = false;
} }
private void PlayButton_Click(object? sender, RoutedEventArgs e)
{
_viewModel.Play();
}
private void StopButton_Click(object? sender, RoutedEventArgs e)
{
_viewModel.Stop();
}
private void PauseButton_Click(object? sender, RoutedEventArgs e)
{
_viewModel.Pause();
}
private void PreviousSongButton_Click(object? sender, RoutedEventArgs e)
{
_viewModel.Previous();
}
private void NextSongButton_Click(object sender, RoutedEventArgs e)
{
_viewModel.Next();
}
} }