using CommunityToolkit.Mvvm.Input; using Harmonia.Core.Engine; using Harmonia.Core.Models; using Harmonia.Core.Player; using Harmonia.WinUI.Caching; using Harmonia.WinUI.Models; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media.Imaging; using System; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; using Windows.System; using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue; namespace Harmonia.WinUI.ViewModels; public partial class PlayerViewModel : ViewModelBase { private readonly IAudioPlayer _audioPlayer; private readonly IAudioBitmapImageCache _audioBitmapImageCache; private readonly DispatcherTimer _timer; private readonly DispatcherQueue _dispatcherQueue; private CancellationTokenSource? _songImageCancellationTokenSource; private Song? _song; public Song? Song { get { return _song; } private set { SetProperty(ref _song, value); CurrentPosition = 0; // What if player is being loaded and returning to save state? Position = 0; MaxPosition = value?.Length.TotalSeconds ?? 0; } } private BitmapImage? _songImageSource; public BitmapImage? SongImageSource { get { return _songImageSource; } private set { SetProperty(ref _songImageSource, value); } } private double _currentPosition; public double CurrentPosition { get { return _currentPosition; } set { SetProperty(ref _currentPosition, value); if (_isPositionChangeInProgress) _audioPlayer.Position = value; } } private double _position; public double Position { get { return _position; } private set { SetProperty(ref _position, value); } } private double _maxPosition; public double MaxPosition { get { return _maxPosition; } set { SetProperty(ref _maxPosition, value); } } private bool _isPositionChangeInProgress; public bool IsPositionChangeInProgress { get { return _isPositionChangeInProgress; } set { SetProperty(ref _isPositionChangeInProgress, value); } } 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 bool CanUpdatePosition { get { return _audioPlayer.State != AudioPlaybackState.Stopped; } private set { 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 PlayerViewModel(IAudioPlayer audioPlayer, IAudioBitmapImageCache audioBitmapCache) { _audioPlayer = audioPlayer; _audioPlayer.PlayingSongChanged += OnPlayingSongChanged; _audioPlayer.PropertyChanged += OnAudioPlayerPropertyChanged; _audioBitmapImageCache = audioBitmapCache; _timer = new() { Interval = TimeSpan.FromMilliseconds(100) }; _timer.Tick += TickTock; _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); } #region Event Handlers private void OnPlayingSongChanged(object? sender, EventArgs e) { Song = _audioPlayer.PlayingSong?.Song; Task.Run(UpdateImage); } private async Task UpdateImage() { // TODO: Show default picture if (Song == null) return; if (_songImageCancellationTokenSource != null) await _songImageCancellationTokenSource.CancelAsync(); _songImageCancellationTokenSource = new(); CancellationToken cancellationToken = _songImageCancellationTokenSource.Token; //BitmapImage? bitmapImage = await _audioBitmapImageCache.GetAsync(Song, cancellationToken); _dispatcherQueue.TryEnqueue(async () => { BitmapImage? bitmapImage = await _audioBitmapImageCache.GetAsync(Song, cancellationToken); SongImageSource = bitmapImage; }); } private void OnAudioPlayerPropertyChanged(object? sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(_audioPlayer.State): UpdateTimer(); OnPropertyChanged(nameof(CanUpdatePosition)); break; } } private void UpdateTimer() { if (_audioPlayer.State == AudioPlaybackState.Playing) { _timer.Start(); } else { _timer.Stop(); } } private void TickTock(object? sender, object e) { Position = _audioPlayer.Position; if (IsPositionChangeInProgress) return; CurrentPosition = _audioPlayer.Position; } #endregion #region Commands 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, }; } #endregion }