// Copyright (c) Arlo Godfrey. All Rights Reserved.
// Licensed under the GNU Lesser General Public License, Version 3.0 with additional terms.
// See the LICENSE, LICENSE.LESSER and LICENSE.ADDITIONAL files in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using StrixMusic.Sdk.AppModels;
using StrixMusic.Sdk.BaseModels;
using StrixMusic.Sdk.CoreModels;
using StrixMusic.Sdk.MediaPlayback;
namespace StrixMusic.Sdk.ViewModels
{
///
/// A ViewModel for .
///
public sealed class DeviceViewModel : ObservableObject, ISdkViewModel, IDevice
{
private readonly SynchronizationContext _syncContext;
private readonly IDevice _model;
private PlaybackItem? _nowPlaying;
///
/// Initializes a new instance of the class.
///
/// The to wrap around.
public DeviceViewModel(IDevice device)
{
_syncContext = SynchronizationContext.Current;
_model = device;
if (_model.NowPlaying != null)
_nowPlaying = _model.NowPlaying;
if (device.SourceCore != null)
SourceCore = new CoreViewModel(device.SourceCore);
if (_model.PlaybackQueue != null)
PlaybackQueue = new TrackCollectionViewModel(_model.PlaybackQueue);
ChangePlaybackSpeedAsyncCommand = new AsyncRelayCommand(ChangePlaybackSpeedAsync);
ResumeAsyncCommand = new AsyncRelayCommand(ResumeAsync);
PauseAsyncCommand = new AsyncRelayCommand(PauseAsync);
TogglePauseResumeCommand = new AsyncRelayCommand(TogglePauseResume);
NextAsyncCommand = new AsyncRelayCommand(NextAsync);
PreviousAsyncCommand = new AsyncRelayCommand(PreviousAsync);
SeekAsyncCommand = new AsyncRelayCommand(SeekAsync);
ChangeVolumeAsyncCommand = new AsyncRelayCommand(ChangeVolumeAsync);
ToggleShuffleCommandAsync = new AsyncRelayCommand(ToggleShuffleAsync);
ToggleRepeatCommandAsync = new AsyncRelayCommand(ToggleRepeatAsync);
SwitchToAsyncCommand = new AsyncRelayCommand(SwitchToAsync);
AttachEvents();
}
private void AttachEvents()
{
_model.IsActiveChanged += Device_IsActiveChanged;
_model.NowPlayingChanged += Device_NowPlayingChanged;
_model.PlaybackContextChanged += Device_PlaybackContextChanged;
_model.PlaybackSpeedChanged += Device_PlaybackSpeedChanged;
_model.PositionChanged += Device_PositionChanged;
_model.RepeatStateChanged += Device_RepeatStateChanged;
_model.ShuffleStateChanged += Device_ShuffleStateChanged;
_model.PlaybackStateChanged += Device_StateChanged;
_model.VolumeChanged += Device_VolumeChanged;
}
private void DetachEvents()
{
_model.IsActiveChanged -= Device_IsActiveChanged;
_model.NowPlayingChanged -= Device_NowPlayingChanged;
_model.PlaybackContextChanged -= Device_PlaybackContextChanged;
_model.PlaybackSpeedChanged -= Device_PlaybackSpeedChanged;
_model.PositionChanged -= Device_PositionChanged;
_model.RepeatStateChanged -= Device_RepeatStateChanged;
_model.ShuffleStateChanged -= Device_ShuffleStateChanged;
_model.PlaybackStateChanged -= Device_StateChanged;
_model.VolumeChanged -= Device_VolumeChanged;
}
private void Device_VolumeChanged(object sender, double e) => _syncContext.Post(_ => OnPropertyChanged(nameof(Volume)), null);
private void Device_ShuffleStateChanged(object sender, bool e) => _syncContext.Post(_ => OnPropertyChanged(nameof(ShuffleState)), null);
private void Device_RepeatStateChanged(object sender, RepeatState e) => _syncContext.Post(_ => OnPropertyChanged(nameof(RepeatState)), null);
private void Device_PositionChanged(object sender, TimeSpan e) => _syncContext.Post(_ => OnPropertyChanged(nameof(Position)), null);
private void Device_PlaybackSpeedChanged(object sender, double e) => _syncContext.Post(_ => OnPropertyChanged(nameof(PlaybackSpeed)), null);
private void Device_PlaybackContextChanged(object sender, IPlayableBase? e) => _syncContext.Post(_ => OnPropertyChanged(nameof(PlaybackContext)), null);
private void Device_IsActiveChanged(object sender, bool e)
{
_syncContext.Post(_ => OnPropertyChanged(nameof(IsActive)), null);
IsActiveChanged?.Invoke(this, e);
}
private void Device_StateChanged(object sender, PlaybackState e) => _syncContext.Post(_ =>
{
OnPropertyChanged(nameof(PlaybackState));
OnPropertyChanged(nameof(IsPlaying));
}, null);
private void Device_NowPlayingChanged(object sender, PlaybackItem e) => _syncContext.Post(_ =>
{
Guard.IsNotNull(e.Track, nameof(e.Track));
NowPlaying = e with
{
Track = new TrackViewModel(e.Track)
};
NowPlayingChanged?.Invoke(sender, e);
}, null);
///
public ICore? SourceCore { get; set; }
///
public ICoreDevice? Source => _model.Source;
///
public string Id => _model.Id;
///
public string Name => _model.Name;
///
public DeviceType Type => _model.Type;
///
public ITrackCollection? PlaybackQueue { get; }
///
public PlaybackItem? NowPlaying
{
get => _nowPlaying;
set => SetProperty(ref _nowPlaying, value);
}
///
public bool IsActive => _model.IsActive;
///
public IPlayableBase? PlaybackContext => _model.PlaybackContext;
///
public TimeSpan Position => _model.Position;
///
public PlaybackState PlaybackState => _model.PlaybackState;
///
/// Indicates if the device is currently playing.
///
public bool IsPlaying => PlaybackState == PlaybackState.Playing;
///
public bool ShuffleState => _model.ShuffleState;
///
public RepeatState RepeatState => _model.RepeatState;
///
public double Volume => _model.Volume;
///
public double PlaybackSpeed => _model.PlaybackSpeed;
///
public bool IsToggleShuffleAsyncAvailable => _model.IsToggleShuffleAsyncAvailable;
///
public bool IsToggleRepeatAsyncAvailable => _model.IsToggleRepeatAsyncAvailable;
///
public bool IsChangeVolumeAsyncAvailable => _model.IsChangeVolumeAsyncAvailable;
///
public bool IsChangePlaybackSpeedAvailable => _model.IsChangePlaybackSpeedAvailable;
///
public bool IsResumeAsyncAvailable => _model.IsResumeAsyncAvailable;
///
public bool IsPauseAsyncAvailable => _model.IsPauseAsyncAvailable;
///
public bool IsNextAsyncAvailable => _model.IsNextAsyncAvailable;
///
public bool IsPreviousAsyncAvailable => _model.IsPreviousAsyncAvailable;
///
public bool IsSeekAsyncAvailable => _model.IsSeekAsyncAvailable;
///
public event EventHandler? IsActiveChanged;
///
public event EventHandler? PlaybackContextChanged
{
add => _model.PlaybackContextChanged += value;
remove => _model.PlaybackContextChanged -= value;
}
///
public event EventHandler? NowPlayingChanged;
///
public event EventHandler? PositionChanged
{
add => _model.PositionChanged += value;
remove => _model.PositionChanged -= value;
}
///
public event EventHandler? PlaybackStateChanged
{
add => _model.PlaybackStateChanged += value;
remove => _model.PlaybackStateChanged -= value;
}
///
public event EventHandler? ShuffleStateChanged
{
add => _model.ShuffleStateChanged += value;
remove => _model.ShuffleStateChanged -= value;
}
///
public event EventHandler? RepeatStateChanged
{
add => _model.RepeatStateChanged += value;
remove => _model.RepeatStateChanged -= value;
}
///
public event EventHandler? VolumeChanged
{
add => _model.VolumeChanged += value;
remove => _model.VolumeChanged -= value;
}
///
public event EventHandler? PlaybackSpeedChanged
{
add => _model.PlaybackSpeedChanged += value;
remove => _model.PlaybackSpeedChanged -= value;
}
///
public Task ChangePlaybackSpeedAsync(double speed, CancellationToken cancellationToken = default) => _model.ChangePlaybackSpeedAsync(speed, cancellationToken);
///
public Task ChangeVolumeAsync(double volume, CancellationToken cancellationToken = default) => _model.ChangeVolumeAsync(volume, cancellationToken);
///
public Task NextAsync(CancellationToken cancellationToken = default) => _model.NextAsync(cancellationToken);
///
public Task PauseAsync(CancellationToken cancellationToken = default) => _model.PauseAsync(cancellationToken);
///
public Task PreviousAsync(CancellationToken cancellationToken = default) => _model.PreviousAsync(cancellationToken);
///
public Task ResumeAsync(CancellationToken cancellationToken = default) => _model.ResumeAsync(cancellationToken);
///
public Task SeekAsync(TimeSpan position, CancellationToken cancellationToken = default) => _model.SeekAsync(position, cancellationToken);
///
public Task SwitchToAsync(CancellationToken cancellationToken = default) => _model.SwitchToAsync(cancellationToken);
///
public Task ToggleRepeatAsync(CancellationToken cancellationToken = default) => _model.ToggleRepeatAsync(cancellationToken);
///
public Task ToggleShuffleAsync(CancellationToken cancellationToken = default) => _model.ToggleShuffleAsync(cancellationToken);
private Task TogglePauseResume(CancellationToken cancellationToken = default) => IsPlaying ? PauseAsync(cancellationToken) : ResumeAsync(cancellationToken);
///
/// Attempts to change playback speed.
///
public IAsyncRelayCommand ChangePlaybackSpeedAsyncCommand { get; }
///
/// Attempts to change the shuffle state for this device.
///
public IAsyncRelayCommand ToggleShuffleCommandAsync { get; }
///
/// Attempts to toggle the repeat state for this device.
///
public IAsyncRelayCommand ToggleRepeatCommandAsync { get; }
///
/// Attempts to switch playback to this device.
///
public IAsyncRelayCommand SwitchToAsyncCommand { get; }
///
/// Attempts to seek the currently playing track on the device. Does not alter playback state.
///
public IAsyncRelayCommand SeekAsyncCommand { get; }
///
/// Attempts to pause the device.
///
public IAsyncRelayCommand ResumeAsyncCommand { get; }
///
/// Attempts to pause the device.
///
public IAsyncRelayCommand PauseAsyncCommand { get; }
///
/// runs if playing, otherwise runs .
///
public IAsyncRelayCommand TogglePauseResumeCommand { get; }
///
/// Attempts to move back to the previous track in the queue.
///
public IAsyncRelayCommand PreviousAsyncCommand { get; }
///
/// Attempts to skip to the next track in the queue.
///
public IAsyncRelayCommand NextAsyncCommand { get; }
///
/// Attempts to change volume.
///
public IAsyncRelayCommand ChangeVolumeAsyncCommand { get; }
///
public ValueTask DisposeAsync()
{
DetachEvents();
return _model.DisposeAsync();
}
}
}