using Avalonia.Media.Imaging; using Avalonia.Threading; using CommunityToolkit.Mvvm.Input; using Harmonia.Core.Models; using Harmonia.Core.Player; using Harmonia.Core.Playlists; using Harmonia.Core.Scanner; using Harmonia.UI.Caching; using Harmonia.UI.Models; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; namespace Harmonia.UI.ViewModels; public partial class PlaybackBarViewModel : ViewModelBase, IDisposable { private readonly IAudioPlayer _audioPlayer; private readonly IAudioBitmapCache _audioBitmapCache; private readonly DispatcherTimer _timer; private CancellationTokenSource? _audioImageCancellationTokenSource; private Song? _song; public Song? Song { get { return _song; } private set { _song = value; OnPropertyChanged(); OnPropertyChanged(nameof(FormattedSongInfo)); CurrentPosition = 0; // What if player is being loaded and returning to save state? Position = 0; MaxPosition = value?.Length.TotalSeconds ?? 0; } } private Bitmap? _songImageSource; public Bitmap? SongImageSource { get { return _songImageSource; } private set { _songImageSource = value; OnPropertyChanged(); } } public string FormattedSongInfo => Song != null ? $"{Song.FileType} - {Song.BitRate} kbps - {Song.SampleRate} Hz" : string.Empty; private double _currentPosition; public double CurrentPosition { get { return _currentPosition; } set { _currentPosition = value; OnPropertyChanged(); if (_isPositionChangeInProgress) _audioPlayer.Position = value; } } private double _position; public double Position { get { return _position; } private set { _position = value; OnPropertyChanged(); } } private double _maxPosition; public double MaxPosition { get { return _maxPosition; } set { _maxPosition = value; OnPropertyChanged(); } } private bool _isPositionChangeInProgress; public bool IsPositionChangeInProgress { get { return _isPositionChangeInProgress; } set { _isPositionChangeInProgress = value; OnPropertyChanged(); } } public double Volume { get { return _audioPlayer.Volume; } set { if (IsMuted) IsMuted = false; _audioPlayer.Volume = value; OnPropertyChanged(); OnPropertyChanged(nameof(VolumeState)); } } public bool IsMuted { get { return _audioPlayer.IsMuted; } set { _audioPlayer.IsMuted = value; OnPropertyChanged(); OnPropertyChanged(nameof(VolumeState)); } } public VolumeState VolumeState { get { if (IsMuted) return VolumeState.Muted; if (Volume == 0) return VolumeState.Off; if (Volume < .33) return VolumeState.Low; if (Volume < .66) return VolumeState.Medium; return VolumeState.High; } } public bool IsRandom { get { return _audioPlayer.IsRandom; } private set { _audioPlayer.IsRandom = value; OnPropertyChanged(); } } public RepeatState RepeatState { get { return _audioPlayer.RepeatState; } private set { _audioPlayer.RepeatState = value; OnPropertyChanged(); } } 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 ICommand ToggleMuteCommand => new RelayCommand(ToggleMute); public ICommand ToggleRandomizerCommand => new RelayCommand(ToggleRandomizer); public ICommand ToggleRepeatCommand => new RelayCommand(ToggleRepeat); public PlaybackBarViewModel(IAudioPlayer audioPlayer, IAudioBitmapCache audioBitmapCache, IPlaylistRepository playlistRepository, IAudioFileScanner audioFileScanner) { _audioPlayer = audioPlayer; _audioPlayer.PlayingSongChanged += OnAudioPlayerPlayingSongChanged; _audioBitmapCache = audioBitmapCache; _timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Default, TickTock); Task.Run(() => PlayDemoSong(playlistRepository)); } private async Task PlayDemoSong(IPlaylistRepository playlistRepository) { if (playlistRepository.Get().Count == 0) { playlistRepository.AddPlaylist(); } Playlist playlist = playlistRepository.Get().First(); if (playlist.Songs.Count > 0) await _audioPlayer.LoadAsync(playlist.Songs[0], PlaybackMode.LoadOnly); } 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; Bitmap? bitmap = await _audioBitmapCache.GetAsync(Song, cancellationToken); await Dispatcher.UIThread.InvokeAsync(() => SetSongImageSource(bitmap)); } private void SetSongImageSource(Bitmap? bitmap) { SongImageSource = bitmap; } private void TickTock(object? sender, object e) { Position = _audioPlayer.Position; if (IsPositionChangeInProgress) return; CurrentPosition = _audioPlayer.Position; } public void Play() { _audioPlayer.Play(); } public void Pause() { _audioPlayer.Pause(); } public void Stop() { _audioPlayer.Stop(); CurrentPosition = 0; Position = 0; } public void Previous() { _audioPlayer.PreviousAsync(); } public void Next() { _audioPlayer.NextAsync(); } public void ToggleMute() { IsMuted = !IsMuted; } public void ToggleRandomizer() { IsRandom = !IsRandom; } public void ToggleRepeat() { RepeatState = GetNextRepeatState(); } private RepeatState GetNextRepeatState() { return _audioPlayer.RepeatState switch { RepeatState.Off => RepeatState.RepeatAll, RepeatState.RepeatAll => RepeatState.RepeatOne, RepeatState.RepeatOne => RepeatState.Off, _ => _audioPlayer.RepeatState, }; } public void Dispose() { SongImageSource?.Dispose(); GC.SuppressFinalize(this); } }