// 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(); } } }