using Avalonia.Media.Imaging; using Avalonia.Threading; using CommunityToolkit.Mvvm.Input; using Harmonia.Core.Caching; using Harmonia.Core.Imaging; using Harmonia.Core.Models; using Harmonia.Core.Player; using Harmonia.Core.Playlists; using Harmonia.Core.Scanner; using Harmonia.UI.Models; using System; using System.IO; 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 IAudioImageCache _audioImageCache; 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?.Dispose(); _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, IAudioImageCache audioImageCache, IPlaylistRepository playlistRepository, IAudioFileScanner audioFileScanner) { _audioPlayer = audioPlayer; _audioPlayer.PlayingSongChanged += OnAudioPlayerPlayingSongChanged; _audioImageCache = audioImageCache; _timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Default, TickTock); } 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); } 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); } }