Updated SongPictureInfo use to async copy of picture data stream. Update other classes accordingly.
This commit is contained in:
@@ -30,11 +30,11 @@ public class AudioImageMemoryCache(IAudioImageExtractor audioImageExtractor) : M
|
||||
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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
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)
|
||||
{
|
||||
using MemoryStream memoryStream = new(songTagInfo.FrontCover.Data);
|
||||
|
||||
return SongPictureInfo.FromStream(memoryStream);
|
||||
return await SongPictureInfo.FromStreamAsync(memoryStream, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -68,7 +68,7 @@ public class AudioImageExtractor : IAudioImageExtractor
|
||||
if (string.IsNullOrWhiteSpace(imagePath))
|
||||
return null;
|
||||
|
||||
return SongPictureInfo.FromFile(path);
|
||||
return await SongPictureInfo.FromFileAsync(path, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace Harmonia.Core.Imaging;
|
||||
|
||||
public interface IAudioImageExtractor
|
||||
{
|
||||
SongPictureInfo? ExtractImage(string path);
|
||||
SongPictureInfo? ExtractImage(string path, SongTagInfo songTagInfo);
|
||||
Task<SongPictureInfo?> ExtractImageAsync(string path, CancellationToken cancellationToken);
|
||||
Task<SongPictureInfo?> ExtractImageAsync(string path, SongTagInfo songTagInfo, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -2,23 +2,23 @@
|
||||
|
||||
namespace Harmonia.Core.Imaging;
|
||||
|
||||
public class SongPictureInfo : IDisposable
|
||||
public class SongPictureInfo
|
||||
{
|
||||
public required Stream Stream { get; init; }
|
||||
public string? ImageHash { get; init; }
|
||||
public required byte[] Data { get; init; }
|
||||
public string? ImageHash { get; private set; }
|
||||
public required string ImageName { get; init; }
|
||||
public long Size { get; init; }
|
||||
public long Size => Data.Length;
|
||||
|
||||
private SongPictureInfo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static SongPictureInfo FromStream(Stream stream)
|
||||
public static async Task<SongPictureInfo> FromStreamAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
string imageHash = ComputeImageHash(stream);
|
||||
|
||||
return GetSongPictureInfo(stream, "Embedded", imageHash);
|
||||
return await GetSongPictureInfoAsync(stream, "Embedded", imageHash, cancellationToken);
|
||||
}
|
||||
|
||||
private static string ComputeImageHash(Stream stream)
|
||||
@@ -29,31 +29,30 @@ public class SongPictureInfo : IDisposable
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
SongPictureInfo songPictureInfo = new()
|
||||
{
|
||||
Stream = stream,
|
||||
ImageHash = imageHash,
|
||||
ImageName = imageName,
|
||||
Size = stream.Length
|
||||
};
|
||||
using MemoryStream memoryStream = new();
|
||||
await stream.CopyToAsync(memoryStream, cancellationToken);
|
||||
|
||||
return songPictureInfo;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stream.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Harmonia.Core.Library;
|
||||
|
||||
public interface IAudioLibrary
|
||||
{
|
||||
void AddLocation(string path);
|
||||
ValueTask AddLocationAsync(string path, CancellationToken cancellationToken);
|
||||
void RemoveLocation(string path);
|
||||
void RemoveLocations(string[] paths);
|
||||
void Scan(string path);
|
||||
@@ -25,12 +25,12 @@ public abstract class AudioLibrary : IAudioLibrary
|
||||
public event EventHandler<EventArgs>? ScanStarted;
|
||||
public event EventHandler<EventArgs>? ScanFinished;
|
||||
|
||||
protected abstract bool IncludeLocation(string path);
|
||||
protected abstract void ScanLocation(string path);
|
||||
protected abstract ValueTask<bool> IncludeLocationAsync(string path, CancellationToken cancellationToken);
|
||||
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;
|
||||
|
||||
LocationAdded?.Invoke(this, new EventArgs());
|
||||
@@ -69,13 +69,13 @@ public abstract class AudioLibrary : IAudioLibrary
|
||||
|
||||
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)
|
||||
return false;
|
||||
|
||||
Location location = GetLocation(path) ?? CreateLocation(path);
|
||||
ScanLocation(location);
|
||||
await ScanLocationAsync(location, cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -104,19 +104,19 @@ public class AudioDatabaseLibrary(IAudioFileScanner audioFileScanner, AudioLibra
|
||||
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);
|
||||
|
||||
if (location == null)
|
||||
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);
|
||||
AddSongs(songModels);
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Harmonia.Core.Scanner;
|
||||
|
||||
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 = [];
|
||||
|
||||
@@ -16,7 +16,7 @@ public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver
|
||||
if (string.IsNullOrWhiteSpace(fileName) || File.Exists(fileName) == false)
|
||||
continue;
|
||||
|
||||
Song? song = GetSong(fileName);
|
||||
Song? song = await GetSongAsync(fileName, cancellationToken);
|
||||
|
||||
if (song == null)
|
||||
continue;
|
||||
@@ -27,11 +27,12 @@ public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver
|
||||
return [.. songs];
|
||||
}
|
||||
|
||||
private Song GetSong(string fileName)
|
||||
private async Task<Song> GetSongAsync(string fileName, CancellationToken cancellationToken)
|
||||
{
|
||||
FileInfo fileInfo = new(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()
|
||||
{
|
||||
@@ -56,12 +57,12 @@ public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver
|
||||
return song;
|
||||
}
|
||||
|
||||
public Song[] GetSongsFromPath(string path)
|
||||
public async Task<Song[]> GetSongsFromPathAsync(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
FileInfo[] fileInfoList = GetAllFilesFromDirectory(path);
|
||||
string[] fileNames = [.. fileInfoList.Select(x => x.FullName)];
|
||||
|
||||
return GetSongs(fileNames);
|
||||
return await GetSongsAsync(fileNames, cancellationToken);
|
||||
}
|
||||
|
||||
private FileInfo[] GetAllFilesFromDirectory(string directoryName)
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace Harmonia.Core.Scanner;
|
||||
|
||||
public interface IAudioFileScanner
|
||||
{
|
||||
Song[] GetSongs(string[] fileNames);
|
||||
Song[] GetSongsFromPath(string path);
|
||||
Task<Song[]> GetSongsAsync(string[] fileNames, CancellationToken cancellationToken);
|
||||
Task<Song[]> GetSongsFromPathAsync(string path, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Harmonia.Core.Caching;
|
||||
using Harmonia.Core.Imaging;
|
||||
using Harmonia.Core.Models;
|
||||
@@ -8,9 +9,11 @@ using Harmonia.Core.Player;
|
||||
using Harmonia.Core.Playlists;
|
||||
using Harmonia.Core.Scanner;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Harmonia.UI.ViewModels;
|
||||
|
||||
@@ -33,6 +36,7 @@ public partial class PlaybackBarViewModel : ViewModelBase
|
||||
{
|
||||
_song = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(FormattedSongInfo));
|
||||
|
||||
CurrentPosition = 0; // What if player is being loaded and returning to save state?
|
||||
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;
|
||||
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)
|
||||
{
|
||||
@@ -133,6 +145,27 @@ public partial class PlaybackBarViewModel : ViewModelBase
|
||||
_audioImageCache = audioImageCache;
|
||||
|
||||
_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)
|
||||
@@ -163,7 +196,11 @@ public partial class PlaybackBarViewModel : ViewModelBase
|
||||
|
||||
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)
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
<vm:MainViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<views:PlaybackBar></views:PlaybackBar>
|
||||
</StackPanel>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<views:PlaybackBar Grid.Row="1"></views:PlaybackBar>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -14,80 +14,94 @@
|
||||
<converter:NullVisibilityConverter x:Key="NullVisibility" />
|
||||
<converter:SongTitleConverter x:Key="SongTitle" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<UserControl.Styles>
|
||||
<!-- Flat Button -->
|
||||
<Style Selector="Button.Flat">
|
||||
<Setter Property="Padding" Value="10"/>
|
||||
<Setter Property="Margin" Value="8 0 8 0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<!-- Slider -->
|
||||
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="0 0 0 10">
|
||||
<!-- Flat Button Path Icon -->
|
||||
<Style Selector="PathIcon.FlatButtonIcon">
|
||||
<Setter Property="Padding" Value="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>
|
||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
</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 -->
|
||||
<Grid Grid.Row="1" Grid.Column="0" Name="PlayingSongGrid">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Grid Margin="0 0 10 0">
|
||||
<!--<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" IsVisible="{Binding Song, Converter={StaticResource NullVisibility}}" FontWeight="Normal" Opacity="1.0">
|
||||
<Run Text="{Binding Song.FileType}"></Run>
|
||||
<Run Text=" - "></Run>
|
||||
<Run Text="{Binding Song.BitRate}"></Run>
|
||||
<Run Text=" kbps - "></Run>
|
||||
<Run Text="{Binding Song.SampleRate}"></Run>
|
||||
<Run Text=" Hz"></Run>
|
||||
</TextBlock>
|
||||
<!-- Slider -->
|
||||
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="0 0 0 10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</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 -->
|
||||
<Grid Grid.Row="1" Grid.Column="0" Name="PlayingSongGrid">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Grid Margin="0 0 10 0">
|
||||
<!--<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>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<Grid Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Margin="8 0 8 0" Click="PreviousSongButton_Click" Theme="{StaticResource BorderlessButton}">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<PathIcon Data="{StaticResource SemiIconBackward}" Margin="0 0 0 0"></PathIcon>
|
||||
<TextBlock Text="" FontSize="18"/>
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
<Button Margin="8 0 8 0" Click="StopButton_Click">
|
||||
<TextBlock Text="B" FontSize="18"></TextBlock>
|
||||
</Button>
|
||||
<Button Margin="8 0 8 0" Click="PlayButton_Click">
|
||||
<TextBlock Text="A" FontSize="36"></TextBlock>
|
||||
</Button>
|
||||
<Button Margin="8 0 8 0" Click="PauseButton_Click">
|
||||
<TextBlock Text="C" FontSize="18"></TextBlock>
|
||||
</Button>
|
||||
<Button Margin="8 0 8 0" Click="NextSongButton_Click">
|
||||
<TextBlock Text="E" FontSize="18"></TextBlock>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<!-- Action Buttons -->
|
||||
<Grid Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Classes="Flat" Command="{Binding PreviousSongCommand}">
|
||||
<PathIcon Classes="FlatButtonIcon" Data="{StaticResource SemiIconBackward}"></PathIcon>
|
||||
</Button>
|
||||
<Button Classes="Flat" Command="{Binding StopSongCommand}">
|
||||
<PathIcon Classes="FlatButtonIcon" Data="{StaticResource SemiIconStop}"></PathIcon>
|
||||
</Button>
|
||||
<Button Classes="Flat" Command="{Binding PlaySongCommand}">
|
||||
<PathIcon Classes="FlatButtonIcon Large" Data="{StaticResource SemiIconPlay}"></PathIcon>
|
||||
</Button>
|
||||
<Button Classes="Flat" Command="{Binding PauseSongCommand}">
|
||||
<PathIcon Classes="FlatButtonIcon" Data="{StaticResource SemiIconPause}"></PathIcon>
|
||||
</Button>
|
||||
<Button Classes="Flat" Command="{Binding NextSongCommand}">
|
||||
<PathIcon Classes="FlatButtonIcon" Data="{StaticResource SemiIconFastForward}"></PathIcon>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
||||
@@ -45,29 +45,4 @@ public partial class PlaybackBar : UserControl
|
||||
_viewModel.CurrentPosition = slider.Value;
|
||||
_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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user