Added playing song info view and view model.
This commit is contained in:
@@ -60,7 +60,7 @@ public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver
|
|||||||
public async Task<Song[]> GetSongsFromPathAsync(string path, CancellationToken cancellationToken)
|
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).OrderBy(x => x)];
|
||||||
|
|
||||||
return await GetSongsAsync(fileNames, cancellationToken);
|
return await GetSongsAsync(fileNames, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public partial class App : Application
|
|||||||
services.AddSingleton<MainViewModel>();
|
services.AddSingleton<MainViewModel>();
|
||||||
services.AddSingleton<MainWindow>();
|
services.AddSingleton<MainWindow>();
|
||||||
services.AddSingleton<PlaybackBarViewModel>();
|
services.AddSingleton<PlaybackBarViewModel>();
|
||||||
|
services.AddSingleton<PlayingSongInfoViewModel>();
|
||||||
|
|
||||||
services.AddHarmonia();
|
services.AddHarmonia();
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ public partial class App : Application
|
|||||||
// DataContext = new MainViewModel()
|
// DataContext = new MainViewModel()
|
||||||
//};
|
//};
|
||||||
|
|
||||||
singleViewPlatform.MainView = ServiceProvider.GetRequiredService<MainWindow>();
|
singleViewPlatform.MainView = ServiceProvider.GetRequiredService<MainView>();
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
|||||||
@@ -27,6 +27,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Update="Views\PlayingSongInfo.axaml.cs">
|
||||||
|
<DependentUpon>PlayingSongInfo.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Update="Views\PlaybackBar.axaml.cs">
|
<Compile Update="Views\PlaybackBar.axaml.cs">
|
||||||
<DependentUpon>PlaybackBar.axaml</DependentUpon>
|
<DependentUpon>PlaybackBar.axaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Media.Imaging;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Harmonia.Core.Caching;
|
using Harmonia.Core.Caching;
|
||||||
@@ -145,27 +144,6 @@ 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)
|
||||||
|
|||||||
91
Harmonia.UI/ViewModels/PlayingSongInfoViewModel.cs
Normal file
91
Harmonia.UI/ViewModels/PlayingSongInfoViewModel.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Harmonia.Core.Caching;
|
||||||
|
using Harmonia.Core.Imaging;
|
||||||
|
using Harmonia.Core.Models;
|
||||||
|
using Harmonia.Core.Player;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Harmonia.UI.ViewModels;
|
||||||
|
|
||||||
|
public class PlayingSongInfoViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IAudioPlayer _audioPlayer;
|
||||||
|
private readonly IAudioImageCache _audioImageCache;
|
||||||
|
|
||||||
|
private CancellationTokenSource? _audioImageCancellationTokenSource;
|
||||||
|
|
||||||
|
private Song? _song;
|
||||||
|
public Song? Song
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _song;
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_song = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap? _songImageSource;
|
||||||
|
public Bitmap? SongImageSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _songImageSource;
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_songImageSource = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayingSongInfoViewModel(IAudioPlayer audioPlayer, IAudioImageCache audioImageCache)
|
||||||
|
{
|
||||||
|
_audioPlayer = audioPlayer;
|
||||||
|
_audioPlayer.PlayingSongChanged += OnAudioPlayerPlayingSongChanged;
|
||||||
|
|
||||||
|
_audioImageCache = audioImageCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Song = _audioPlayer.PlayingSong?.Song;
|
||||||
|
Task.Run(UpdateImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateImage()
|
||||||
|
{
|
||||||
|
// TODO: Show default picture
|
||||||
|
if (Song == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_audioImageCancellationTokenSource != null)
|
||||||
|
await _audioImageCancellationTokenSource.CancelAsync();
|
||||||
|
|
||||||
|
_audioImageCancellationTokenSource = new();
|
||||||
|
CancellationToken cancellationToken = _audioImageCancellationTokenSource.Token;
|
||||||
|
|
||||||
|
SongPictureInfo? songPictureInfo = await _audioImageCache.GetAsync(Song, cancellationToken);
|
||||||
|
|
||||||
|
if (songPictureInfo == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(() => SetSongImageSource(songPictureInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetSongImageSource(SongPictureInfo songPictureInfo)
|
||||||
|
{
|
||||||
|
if (songPictureInfo.Data.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using MemoryStream stream = new(songPictureInfo.Data);
|
||||||
|
SongImageSource = new(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,4 +9,7 @@ public class ViewModelLocator
|
|||||||
|
|
||||||
public static PlaybackBarViewModel PlaybackBarViewModel
|
public static PlaybackBarViewModel PlaybackBarViewModel
|
||||||
=> App.ServiceProvider.GetRequiredService<PlaybackBarViewModel>();
|
=> App.ServiceProvider.GetRequiredService<PlaybackBarViewModel>();
|
||||||
|
|
||||||
|
public static PlayingSongInfoViewModel PlayingSongInfoViewModel
|
||||||
|
=> App.ServiceProvider.GetRequiredService<PlayingSongInfoViewModel>();
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<RowDefinition Height="*"></RowDefinition>
|
<RowDefinition Height="*"></RowDefinition>
|
||||||
<RowDefinition Height="Auto"></RowDefinition>
|
<RowDefinition Height="Auto"></RowDefinition>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
<views:PlayingSongInfo Grid.Row="0"></views:PlayingSongInfo>
|
||||||
<views:PlaybackBar Grid.Row="1"></views:PlaybackBar>
|
<views:PlaybackBar Grid.Row="1"></views:PlaybackBar>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,61 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Harmonia.Core.Engine;
|
||||||
|
using Harmonia.Core.Models;
|
||||||
|
using Harmonia.Core.Player;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Harmonia.UI.Views;
|
namespace Harmonia.UI.Views;
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
public MainWindow()
|
private readonly IAudioPlayer _audioPlayer;
|
||||||
|
|
||||||
|
private const string ApplicationTitle = "Harmonia";
|
||||||
|
|
||||||
|
public MainWindow(IAudioPlayer audioPlayer)
|
||||||
{
|
{
|
||||||
|
_audioPlayer = audioPlayer;
|
||||||
|
_audioPlayer.PropertyChanged += OnAudioPlayerPropertyChanged;
|
||||||
|
|
||||||
|
Title = ApplicationTitle;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAudioPlayerPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.PropertyName)
|
||||||
|
{
|
||||||
|
case nameof(_audioPlayer.State):
|
||||||
|
Title = GetUpdatedAppTitle();
|
||||||
|
//PropertyChanged(nameof(Title));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUpdatedAppTitle()
|
||||||
|
{
|
||||||
|
return _audioPlayer.State switch
|
||||||
|
{
|
||||||
|
AudioPlaybackState.Stopped => ApplicationTitle,
|
||||||
|
_ => $"{GetSongArtistsAndTitle()} - {ApplicationTitle}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSongArtistsAndTitle()
|
||||||
|
{
|
||||||
|
if (_audioPlayer.PlayingSong == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
Song song = _audioPlayer.PlayingSong.Song;
|
||||||
|
|
||||||
|
string[] values =
|
||||||
|
[
|
||||||
|
string.Join(" / ", song.Artists ?? song.AlbumArtists),
|
||||||
|
song.Title ?? song.ShortFileName
|
||||||
|
];
|
||||||
|
|
||||||
|
return string.Join(" - ", values.Where(value => string.IsNullOrWhiteSpace(value) == false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public partial class PlaybackBar : UserControl
|
|||||||
_viewModel = (PlaybackBarViewModel)DataContext!;
|
_viewModel = (PlaybackBarViewModel)DataContext!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Slider_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
private void Slider_Loaded(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not Slider slider)
|
if (sender is not Slider slider)
|
||||||
return;
|
return;
|
||||||
|
|||||||
27
Harmonia.UI/Views/PlayingSongInfo.axaml
Normal file
27
Harmonia.UI/Views/PlayingSongInfo.axaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
|
||||||
|
xmlns:converter="clr-namespace:Harmonia.UI.Converters"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
DataContext="{x:Static vm:ViewModelLocator.PlayingSongInfoViewModel}"
|
||||||
|
x:Class="Harmonia.UI.Views.PlayingSongInfo"
|
||||||
|
x:DataType="vm:PlayingSongInfoViewModel">
|
||||||
|
<Grid>
|
||||||
|
<Image Source="{Binding SongImageSource, Mode=OneWay}" Stretch="UniformToFill" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<Image.Effect>
|
||||||
|
<BlurEffect Radius="20" />
|
||||||
|
</Image.Effect>
|
||||||
|
<Image.RenderTransform>
|
||||||
|
<ScaleTransform ScaleX="1.05" ScaleY="1.05" />
|
||||||
|
</Image.RenderTransform>
|
||||||
|
</Image>
|
||||||
|
<Canvas Background="#99000000"></Canvas>
|
||||||
|
<Image Source="{Binding SongImageSource}" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20">
|
||||||
|
<Image.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="10" Opacity=".4" OffsetX="3" OffsetY="3" />
|
||||||
|
</Image.Effect>
|
||||||
|
</Image>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
17
Harmonia.UI/Views/PlayingSongInfo.axaml.cs
Normal file
17
Harmonia.UI/Views/PlayingSongInfo.axaml.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Harmonia.UI.ViewModels;
|
||||||
|
|
||||||
|
namespace Harmonia.UI.Views;
|
||||||
|
|
||||||
|
public partial class PlayingSongInfo : UserControl
|
||||||
|
{
|
||||||
|
private readonly PlayingSongInfoViewModel _viewModel;
|
||||||
|
|
||||||
|
public PlayingSongInfo()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_viewModel = (PlayingSongInfoViewModel)DataContext!;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user