diff --git a/RedBookPlayer/GUI/PlayerView.xaml.cs b/RedBookPlayer/GUI/PlayerView.xaml.cs index b3c4166..6989a33 100644 --- a/RedBookPlayer/GUI/PlayerView.xaml.cs +++ b/RedBookPlayer/GUI/PlayerView.xaml.cs @@ -12,16 +12,15 @@ using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Threading; -using RedBookPlayer.Hardware; namespace RedBookPlayer.GUI { public class PlayerView : UserControl { /// - /// Player representing the internal state + /// Read-only access to the view model /// - public static Player Player = new Player(); + public PlayerViewModel PlayerViewModel => DataContext as PlayerViewModel; /// /// Set of images representing the digits for the UI @@ -71,13 +70,12 @@ namespace RedBookPlayer.GUI public async Task LoadImage(string path) { // If the player is currently running, stop it - if((DataContext as PlayerViewModel).Playing != true) - (DataContext as PlayerViewModel).Playing = null; + if(PlayerViewModel.Playing != true) PlayerViewModel.Playing = null; - bool result = await Task.Run(() => + bool result = await Dispatcher.UIThread.InvokeAsync(() => { - Player.Init(path, App.Settings.AutoPlay); - return Player.Initialized; + PlayerViewModel.Init(path, App.Settings.AutoPlay); + return PlayerViewModel.Initialized; }); if(result) @@ -122,7 +120,7 @@ namespace RedBookPlayer.GUI private void InitializeComponent(string xaml) { DataContext = new PlayerViewModel(); - (DataContext as PlayerViewModel).PropertyChanged += UpdateModel; + PlayerViewModel.PropertyChanged += UpdateModel; // Load the theme try @@ -199,7 +197,7 @@ namespace RedBookPlayer.GUI { Dispatcher.UIThread.InvokeAsync(() => { - Player.UpdateModel(DataContext as PlayerViewModel); + PlayerViewModel.UpdateModel(); }); } @@ -210,14 +208,14 @@ namespace RedBookPlayer.GUI { Dispatcher.UIThread.InvokeAsync(() => { - string digitString = Player.GenerateDigitString(); + string digitString = PlayerViewModel.GenerateDigitString(); for (int i = 0; i < _digits.Length; i++) { if (_digits[i] != null) _digits[i].Source = GetBitmap(digitString[i]); } - Player.UpdateDataContext(DataContext as PlayerViewModel); + PlayerViewModel?.UpdateView(); }); } @@ -234,49 +232,55 @@ namespace RedBookPlayer.GUI await LoadImage(path); } - public void PlayButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).Playing = true; + public void PlayButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Playing = true; - public void PauseButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).Playing = false; + public void PauseButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Playing = false; - public void PlayPauseButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).Playing = !(DataContext as PlayerViewModel).Playing; + public void PlayPauseButton_Click(object sender, RoutedEventArgs e) + { + if(PlayerViewModel.Playing == true) + PlayerViewModel.Playing = false; + else + PlayerViewModel.Playing = true; + } - public void StopButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).Playing = null; + public void StopButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Playing = null; - public void NextTrackButton_Click(object sender, RoutedEventArgs e) => Player.NextTrack(); + public void NextTrackButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.NextTrack(); - public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) => Player.PreviousTrack(); + public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.PreviousTrack(); - public void NextIndexButton_Click(object sender, RoutedEventArgs e) => Player.NextIndex(App.Settings.IndexButtonChangeTrack); + public void NextIndexButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.NextIndex(App.Settings.IndexButtonChangeTrack); - public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) => Player.PreviousIndex(App.Settings.IndexButtonChangeTrack); + public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.PreviousIndex(App.Settings.IndexButtonChangeTrack); - public void FastForwardButton_Click(object sender, RoutedEventArgs e) => Player.FastForward(); + public void FastForwardButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.FastForward(); - public void RewindButton_Click(object sender, RoutedEventArgs e) => Player.Rewind(); + public void RewindButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Rewind(); - public void VolumeUpButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).Volume++; + public void VolumeUpButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Volume++; - public void VolumeDownButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).Volume--; + public void VolumeDownButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Volume--; public void MuteToggleButton_Click(object sender, RoutedEventArgs e) { if (_lastVolume == null) { - _lastVolume = (DataContext as PlayerViewModel).Volume; - (DataContext as PlayerViewModel).Volume = 0; + _lastVolume = PlayerViewModel.Volume; + PlayerViewModel.Volume = 0; } else { - (DataContext as PlayerViewModel).Volume = _lastVolume.Value; + PlayerViewModel.Volume = _lastVolume.Value; _lastVolume = null; } } - public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).ApplyDeEmphasis = true; + public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.ApplyDeEmphasis = true; - public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).ApplyDeEmphasis = false; + public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.ApplyDeEmphasis = false; - public void EnableDisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => (DataContext as PlayerViewModel).ApplyDeEmphasis = !(DataContext as PlayerViewModel).ApplyDeEmphasis; + public void EnableDisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.ApplyDeEmphasis = !PlayerViewModel.ApplyDeEmphasis; #endregion } diff --git a/RedBookPlayer/GUI/PlayerViewModel.cs b/RedBookPlayer/GUI/PlayerViewModel.cs index 55cb5d3..a01caf9 100644 --- a/RedBookPlayer/GUI/PlayerViewModel.cs +++ b/RedBookPlayer/GUI/PlayerViewModel.cs @@ -1,11 +1,22 @@ +using System.Linq; +using Aaru.CommonTypes.Enums; using ReactiveUI; +using RedBookPlayer.Discs; +using RedBookPlayer.Hardware; namespace RedBookPlayer.GUI { public class PlayerViewModel : ReactiveObject { + /// + /// Player representing the internal state + /// + private Player _player; + #region Player Status + public bool Initialized => _player?.Initialized ?? false; + private bool? _playing; public bool? Playing { @@ -93,5 +104,137 @@ namespace RedBookPlayer.GUI } #endregion + + /// + /// Initialize the view model with a given image path + /// + /// Path to the disc image + /// True if playback should begin immediately, false otherwise + public void Init(string path, bool autoPlay) + { + _player = new Player(); + _player.Init(path, autoPlay); + + if(Initialized) + UpdateModel(); + } + + #region Playback + + /// + /// Move to the next playable track + /// + public void NextTrack() => _player?.NextTrack(); + + /// + /// Move to the previous playable track + /// + public void PreviousTrack() => _player?.PreviousTrack(); + + /// + /// Move to the next index + /// + /// True if index changes can trigger a track change, false otherwise + public void NextIndex(bool changeTrack) => _player?.NextIndex(changeTrack); + + /// + /// Move to the previous index + /// + /// True if index changes can trigger a track change, false otherwise + public void PreviousIndex(bool changeTrack) => _player?.PreviousIndex(changeTrack); + + /// + /// Fast-forward playback by 75 sectors, if possible + /// + public void FastForward() => _player?.FastForward(); + + /// + /// Rewind playback by 75 sectors, if possible + /// + public void Rewind() => _player?.Rewind(); + + #endregion + + #region Helpers + + /// + /// Generate the digit string to be interpreted by the frontend + /// + /// String representing the digits for the frontend + public string GenerateDigitString() + { + // If the disc isn't initialized, return all '-' characters + if(_player?.OpticalDisc == null || !_player.OpticalDisc.Initialized) + return string.Empty.PadLeft(20, '-'); + + // Otherwise, take the current time into account + ulong sectorTime = _player.GetCurrentSectorTime(); + + int[] numbers = new int[] + { + _player.OpticalDisc.CurrentTrackNumber + 1, + _player.OpticalDisc.CurrentTrackIndex, + + (int)(sectorTime / (75 * 60)), + (int)(sectorTime / 75 % 60), + (int)(sectorTime % 75), + + _player.OpticalDisc.TotalTracks, + _player.OpticalDisc.TotalIndexes, + + (int)(_player.OpticalDisc.TotalTime / (75 * 60)), + (int)(_player.OpticalDisc.TotalTime / 75 % 60), + (int)(_player.OpticalDisc.TotalTime % 75), + }; + + return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2))); + } + + /// + /// Update the UI from the internal player + /// + public void UpdateView() + { + if(_player?.Initialized != true) + return; + + Playing = _player.Playing; + CurrentSector = _player.GetCurrentSectorTime(); + TotalSectors = _player.OpticalDisc.TotalTime; + Volume = App.Settings.Volume; + + ApplyDeEmphasis = _player.ApplyDeEmphasis; + HiddenTrack = _player.OpticalDisc.TimeOffset > 150; + + if(_player.OpticalDisc is CompactDisc compactDisc) + { + QuadChannel = compactDisc.QuadChannel; + IsDataTrack = compactDisc.IsDataTrack; + CopyAllowed = compactDisc.CopyAllowed; + TrackHasEmphasis = compactDisc.TrackHasEmphasis; + } + else + { + QuadChannel = false; + IsDataTrack = _player.OpticalDisc.TrackType != TrackType.Audio; + CopyAllowed = false; + TrackHasEmphasis = false; + } + } + + /// + /// Update the internal player from the UI + /// + public void UpdateModel() + { + if(_player?.Initialized != true) + return; + + _player.SetPlayingState(Playing); + App.Settings.Volume = Volume; + _player.SetDeEmphasis(ApplyDeEmphasis); + } + + #endregion } } \ No newline at end of file diff --git a/RedBookPlayer/Hardware/Player.cs b/RedBookPlayer/Hardware/Player.cs index 5e63a79..c18628e 100644 --- a/RedBookPlayer/Hardware/Player.cs +++ b/RedBookPlayer/Hardware/Player.cs @@ -1,11 +1,8 @@ using System; using System.IO; -using System.Linq; -using Aaru.CommonTypes.Enums; using Aaru.DiscImages; using Aaru.Filters; using RedBookPlayer.Discs; -using RedBookPlayer.GUI; namespace RedBookPlayer.Hardware { @@ -18,10 +15,15 @@ namespace RedBookPlayer.Hardware /// public bool Initialized { get; private set; } = false; + /// + /// OpticalDisc object + /// + public OpticalDisc OpticalDisc { get; private set; } + /// /// Indicate if the disc is playing /// - public bool Playing => _soundOutput?.Playing ?? false; + public bool? Playing => _soundOutput?.Playing; /// /// Indicates if de-emphasis should be applied @@ -32,11 +34,6 @@ namespace RedBookPlayer.Hardware #region Private State Variables - /// - /// OpticalDisc object - /// - private OpticalDisc _opticalDisc; - /// /// Sound output handling class /// @@ -55,7 +52,7 @@ namespace RedBookPlayer.Hardware Initialized = false; _soundOutput = new SoundOutput(); _soundOutput.ApplyDeEmphasis = false; - _opticalDisc = null; + OpticalDisc = null; try { @@ -70,7 +67,7 @@ namespace RedBookPlayer.Hardware image.Open(filter); // Generate and instantiate the disc - _opticalDisc = OpticalDiscFactory.GenerateFromImage(image, App.Settings.AutoPlay); + OpticalDisc = OpticalDiscFactory.GenerateFromImage(image, App.Settings.AutoPlay); } catch { @@ -79,7 +76,7 @@ namespace RedBookPlayer.Hardware } // Initialize the sound output - _soundOutput.Init(_opticalDisc, autoPlay); + _soundOutput.Init(OpticalDisc, autoPlay); if(_soundOutput == null || !_soundOutput.Initialized) return; @@ -93,15 +90,19 @@ namespace RedBookPlayer.Hardware /// Set the current audio playback state /// /// True to start playback, false to pause, null to stop - private void SetPlayingState(bool? start) + public void SetPlayingState(bool? start) { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(OpticalDisc == null || !OpticalDisc.Initialized) + return; + + // If the playing state has not changed, do nothing + if(start == Playing) return; if(start == true) { _soundOutput.Play(); - _opticalDisc.SetTotalIndexes(); + OpticalDisc.SetTotalIndexes(); } else if(start == false) { @@ -110,7 +111,7 @@ namespace RedBookPlayer.Hardware else { _soundOutput.Stop(); - _opticalDisc.LoadFirstTrack(); + OpticalDisc.LoadFirstTrack(); } } @@ -119,17 +120,17 @@ namespace RedBookPlayer.Hardware /// public void NextTrack() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(OpticalDisc == null || !OpticalDisc.Initialized) return; - bool wasPlaying = Playing; - if(wasPlaying) SetPlayingState(false); + bool? wasPlaying = Playing; + if(wasPlaying == true) SetPlayingState(false); - _opticalDisc.NextTrack(); - if(_opticalDisc is CompactDisc compactDisc) + OpticalDisc.NextTrack(); + if(OpticalDisc is CompactDisc compactDisc) _soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - if(wasPlaying) SetPlayingState(true); + if(wasPlaying == true) SetPlayingState(true); } /// @@ -137,17 +138,17 @@ namespace RedBookPlayer.Hardware /// public void PreviousTrack() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(OpticalDisc == null || !OpticalDisc.Initialized) return; - bool wasPlaying = Playing; - if(wasPlaying) SetPlayingState(false); + bool? wasPlaying = Playing; + if(wasPlaying == true) SetPlayingState(false); - _opticalDisc.PreviousTrack(); - if(_opticalDisc is CompactDisc compactDisc) + OpticalDisc.PreviousTrack(); + if(OpticalDisc is CompactDisc compactDisc) _soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - if(wasPlaying) SetPlayingState(true); + if(wasPlaying == true) SetPlayingState(true); } /// @@ -156,17 +157,17 @@ namespace RedBookPlayer.Hardware /// True if index changes can trigger a track change, false otherwise public void NextIndex(bool changeTrack) { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(OpticalDisc == null || !OpticalDisc.Initialized) return; - bool wasPlaying = Playing; - if(wasPlaying) SetPlayingState(false); + bool? wasPlaying = Playing; + if(wasPlaying == true) SetPlayingState(false); - _opticalDisc.NextIndex(changeTrack); - if(_opticalDisc is CompactDisc compactDisc) + OpticalDisc.NextIndex(changeTrack); + if(OpticalDisc is CompactDisc compactDisc) _soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - if(wasPlaying) SetPlayingState(true); + if(wasPlaying == true) SetPlayingState(true); } /// @@ -175,17 +176,17 @@ namespace RedBookPlayer.Hardware /// True if index changes can trigger a track change, false otherwise public void PreviousIndex(bool changeTrack) { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(OpticalDisc == null || !OpticalDisc.Initialized) return; - bool wasPlaying = Playing; - if(wasPlaying) SetPlayingState(false); + bool? wasPlaying = Playing; + if(wasPlaying == true) SetPlayingState(false); - _opticalDisc.PreviousIndex(changeTrack); - if(_opticalDisc is CompactDisc compactDisc) + OpticalDisc.PreviousIndex(changeTrack); + if(OpticalDisc is CompactDisc compactDisc) _soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - if(wasPlaying) SetPlayingState(true); + if(wasPlaying == true) SetPlayingState(true); } /// @@ -193,10 +194,10 @@ namespace RedBookPlayer.Hardware /// public void FastForward() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(OpticalDisc == null || !OpticalDisc.Initialized) return; - _opticalDisc.CurrentSector = Math.Min(_opticalDisc.TotalSectors, _opticalDisc.CurrentSector + 75); + OpticalDisc.CurrentSector = Math.Min(OpticalDisc.TotalSectors, OpticalDisc.CurrentSector + 75); } /// @@ -204,112 +205,38 @@ namespace RedBookPlayer.Hardware /// public void Rewind() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(OpticalDisc == null || !OpticalDisc.Initialized) return; - if(_opticalDisc.CurrentSector >= 75) - _opticalDisc.CurrentSector -= 75; + if(OpticalDisc.CurrentSector >= 75) + OpticalDisc.CurrentSector -= 75; } #endregion #region Helpers - /// - /// Generate the digit string to be interpreted by the frontend - /// - /// String representing the digits for the frontend - public string GenerateDigitString() - { - // If the disc isn't initialized, return all '-' characters - if(_opticalDisc == null || !_opticalDisc.Initialized) - return string.Empty.PadLeft(20, '-'); - - // Otherwise, take the current time into account - ulong sectorTime = GetCurrentSectorTime(); - - int[] numbers = new int[] - { - _opticalDisc.CurrentTrackNumber + 1, - _opticalDisc.CurrentTrackIndex, - - (int)(sectorTime / (75 * 60)), - (int)(sectorTime / 75 % 60), - (int)(sectorTime % 75), - - _opticalDisc.TotalTracks, - _opticalDisc.TotalIndexes, - - (int)(_opticalDisc.TotalTime / (75 * 60)), - (int)(_opticalDisc.TotalTime / 75 % 60), - (int)(_opticalDisc.TotalTime % 75), - }; - - return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2))); - } - - /// - /// Update the data context for the frontend - /// - /// Data context to be updated - public void UpdateDataContext(PlayerViewModel dataContext) - { - if(!Initialized || dataContext == null) - return; - - dataContext.Playing = Playing; - dataContext.CurrentSector = GetCurrentSectorTime(); - dataContext.TotalSectors = _opticalDisc.TotalTime; - dataContext.Volume = App.Settings.Volume; - - dataContext.ApplyDeEmphasis = ApplyDeEmphasis; - dataContext.HiddenTrack = _opticalDisc.TimeOffset > 150; - - if(_opticalDisc is CompactDisc compactDisc) - { - dataContext.QuadChannel = compactDisc.QuadChannel; - dataContext.IsDataTrack = compactDisc.IsDataTrack; - dataContext.CopyAllowed = compactDisc.CopyAllowed; - dataContext.TrackHasEmphasis = compactDisc.TrackHasEmphasis; - } - else - { - dataContext.QuadChannel = false; - dataContext.IsDataTrack = _opticalDisc.TrackType != TrackType.Audio; - dataContext.CopyAllowed = false; - dataContext.TrackHasEmphasis = false; - } - } - - /// - /// Update the internal values from the frontend - /// - /// Data context to update from - public void UpdateModel(PlayerViewModel dataContext) - { - if(!Initialized || dataContext == null) - return; - - SetPlayingState(dataContext.Playing); - App.Settings.Volume = dataContext.Volume; - _soundOutput?.ToggleDeEmphasis(dataContext.ApplyDeEmphasis); - } - /// /// Get current sector time, accounting for offsets /// /// ulong representing the current sector time - private ulong GetCurrentSectorTime() + public ulong GetCurrentSectorTime() { - ulong sectorTime = _opticalDisc.CurrentSector; - if(_opticalDisc.SectionStartSector != 0) - sectorTime -= _opticalDisc.SectionStartSector; + ulong sectorTime = OpticalDisc.CurrentSector; + if (OpticalDisc.SectionStartSector != 0) + sectorTime -= OpticalDisc.SectionStartSector; else - sectorTime += _opticalDisc.TimeOffset; + sectorTime += OpticalDisc.TimeOffset; return sectorTime; } + /// + /// Set if de-emphasis should be applied + /// + /// True to enable, false to disable + public void SetDeEmphasis(bool apply) => _soundOutput?.ToggleDeEmphasis(apply); + #endregion } } \ No newline at end of file