diff --git a/Harmonia.Core/Scanner/AudioFileScanner.cs b/Harmonia.Core/Scanner/AudioFileScanner.cs index 1922f6d..a4236cc 100644 --- a/Harmonia.Core/Scanner/AudioFileScanner.cs +++ b/Harmonia.Core/Scanner/AudioFileScanner.cs @@ -60,7 +60,7 @@ public class AudioFileScanner(IAudioEngine audioEngine, ITagResolver tagResolver public async Task GetSongsFromPathAsync(string path, CancellationToken cancellationToken) { 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); } diff --git a/Harmonia.UI/App.axaml.cs b/Harmonia.UI/App.axaml.cs index 37b5c63..298ef19 100644 --- a/Harmonia.UI/App.axaml.cs +++ b/Harmonia.UI/App.axaml.cs @@ -21,6 +21,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddHarmonia(); @@ -54,7 +55,7 @@ public partial class App : Application // DataContext = new MainViewModel() //}; - singleViewPlatform.MainView = ServiceProvider.GetRequiredService(); + singleViewPlatform.MainView = ServiceProvider.GetRequiredService(); } base.OnFrameworkInitializationCompleted(); diff --git a/Harmonia.UI/Harmonia.UI.csproj b/Harmonia.UI/Harmonia.UI.csproj index 6e55c98..a06ad3d 100644 --- a/Harmonia.UI/Harmonia.UI.csproj +++ b/Harmonia.UI/Harmonia.UI.csproj @@ -27,6 +27,9 @@ + + PlayingSongInfo.axaml + PlaybackBar.axaml diff --git a/Harmonia.UI/ViewModels/PlaybackBarViewModel.cs b/Harmonia.UI/ViewModels/PlaybackBarViewModel.cs index 671f848..93cd116 100644 --- a/Harmonia.UI/ViewModels/PlaybackBarViewModel.cs +++ b/Harmonia.UI/ViewModels/PlaybackBarViewModel.cs @@ -1,5 +1,4 @@ -using Avalonia.Controls; -using Avalonia.Media.Imaging; +using Avalonia.Media.Imaging; using Avalonia.Threading; using CommunityToolkit.Mvvm.Input; using Harmonia.Core.Caching; @@ -145,27 +144,6 @@ 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) diff --git a/Harmonia.UI/ViewModels/PlayingSongInfoViewModel.cs b/Harmonia.UI/ViewModels/PlayingSongInfoViewModel.cs new file mode 100644 index 0000000..29f815f --- /dev/null +++ b/Harmonia.UI/ViewModels/PlayingSongInfoViewModel.cs @@ -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); + } +} \ No newline at end of file diff --git a/Harmonia.UI/ViewModels/ViewModelLocator.cs b/Harmonia.UI/ViewModels/ViewModelLocator.cs index 833666e..92e0f9a 100644 --- a/Harmonia.UI/ViewModels/ViewModelLocator.cs +++ b/Harmonia.UI/ViewModels/ViewModelLocator.cs @@ -9,4 +9,7 @@ public class ViewModelLocator public static PlaybackBarViewModel PlaybackBarViewModel => App.ServiceProvider.GetRequiredService(); + + public static PlayingSongInfoViewModel PlayingSongInfoViewModel + => App.ServiceProvider.GetRequiredService(); } \ No newline at end of file diff --git a/Harmonia.UI/Views/MainView.axaml b/Harmonia.UI/Views/MainView.axaml index 21bd62e..62c6450 100644 --- a/Harmonia.UI/Views/MainView.axaml +++ b/Harmonia.UI/Views/MainView.axaml @@ -19,6 +19,7 @@ + diff --git a/Harmonia.UI/Views/MainWindow.axaml.cs b/Harmonia.UI/Views/MainWindow.axaml.cs index 6347089..66f5d5c 100644 --- a/Harmonia.UI/Views/MainWindow.axaml.cs +++ b/Harmonia.UI/Views/MainWindow.axaml.cs @@ -1,11 +1,61 @@ 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; 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(); } + + 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)); + } } diff --git a/Harmonia.UI/Views/PlaybackBar.axaml.cs b/Harmonia.UI/Views/PlaybackBar.axaml.cs index 6574f2d..aec8b2d 100644 --- a/Harmonia.UI/Views/PlaybackBar.axaml.cs +++ b/Harmonia.UI/Views/PlaybackBar.axaml.cs @@ -15,7 +15,7 @@ public partial class PlaybackBar : UserControl _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) return; diff --git a/Harmonia.UI/Views/PlayingSongInfo.axaml b/Harmonia.UI/Views/PlayingSongInfo.axaml new file mode 100644 index 0000000..633135c --- /dev/null +++ b/Harmonia.UI/Views/PlayingSongInfo.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/Harmonia.UI/Views/PlayingSongInfo.axaml.cs b/Harmonia.UI/Views/PlayingSongInfo.axaml.cs new file mode 100644 index 0000000..c5123e2 --- /dev/null +++ b/Harmonia.UI/Views/PlayingSongInfo.axaml.cs @@ -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!; + } +} \ No newline at end of file