using System.ComponentModel; using System.Linq; using ReactiveUI; using RedBookPlayer.Hardware; namespace RedBookPlayer.GUI { public class PlayerViewModel : ReactiveObject { /// /// Player representing the internal state /// private Player _player; /// /// Last volume for mute toggling /// private int? _lastVolume = null; #region Player Passthrough #region OpticalDisc Passthrough /// /// Current track number /// public int CurrentTrackNumber { get => _currentTrackNumber; private set => this.RaiseAndSetIfChanged(ref _currentTrackNumber, value); } /// /// Current track index /// public ushort CurrentTrackIndex { get => _currentTrackIndex; private set => this.RaiseAndSetIfChanged(ref _currentTrackIndex, value); } /// /// Current sector number /// public ulong CurrentSector { get => _currentSector; private set => this.RaiseAndSetIfChanged(ref _currentSector, value); } /// /// Represents if the disc has a hidden track /// public bool HiddenTrack { get => _hasHiddenTrack; private set => this.RaiseAndSetIfChanged(ref _hasHiddenTrack, value); } /// /// Represents the 4CH flag [CompactDisc only] /// public bool QuadChannel { get => _quadChannel; private set => this.RaiseAndSetIfChanged(ref _quadChannel, value); } /// /// Represents the DATA flag [CompactDisc only] /// public bool IsDataTrack { get => _isDataTrack; private set => this.RaiseAndSetIfChanged(ref _isDataTrack, value); } /// /// Represents the DCP flag [CompactDisc only] /// public bool CopyAllowed { get => _copyAllowed; private set => this.RaiseAndSetIfChanged(ref _copyAllowed, value); } /// /// Represents the PRE flag [CompactDisc only] /// public bool TrackHasEmphasis { get => _trackHasEmphasis; private set => this.RaiseAndSetIfChanged(ref _trackHasEmphasis, value); } /// /// Represents the total tracks on the disc /// public int TotalTracks => _player.TotalTracks; /// /// Represents the total indices on the disc /// public int TotalIndexes => _player.TotalIndexes; /// /// Total sectors in the image /// public ulong TotalSectors => _player.TotalSectors; /// /// Represents the time adjustment offset for the disc /// public ulong TimeOffset => _player.TimeOffset; /// /// Represents the total playing time for the disc /// public ulong TotalTime => _player.TotalTime; private int _currentTrackNumber; private ushort _currentTrackIndex; private ulong _currentSector; private bool _hasHiddenTrack; private bool _quadChannel; private bool _isDataTrack; private bool _copyAllowed; private bool _trackHasEmphasis; #endregion #region SoundOutput Passthrough /// /// Indicate if the model is ready to be used /// public bool Initialized => _player?.Initialized ?? false; /// /// Indicate if the output is playing /// public bool? Playing { get => _playing; private set => this.RaiseAndSetIfChanged(ref _playing, value); } /// /// Indicates if de-emphasis should be applied /// public bool ApplyDeEmphasis { get => _applyDeEmphasis; private set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value); } /// /// Current playback volume /// public int Volume { get => _volume; set => this.RaiseAndSetIfChanged(ref _volume, value); } private bool? _playing; private bool _applyDeEmphasis; private int _volume; #endregion #endregion /// /// Initialize the view model with a given image path /// /// Path to the disc image /// True if playback should begin immediately, false otherwise /// Default volume between 0 and 100 to use when starting playback public void Init(string path, bool autoPlay, int defaultVolume) { // Stop current playback, if necessary if(Playing != null) Playing = null; // Create and attempt to initialize new Player _player = new Player(path, autoPlay, defaultVolume); if(Initialized) { _player.PropertyChanged += PlayerStateChanged; PlayerStateChanged(this, null); } } #region Playback /// /// Begin playback /// public void Play() => _player.Play(); /// /// Pause current playback /// public void Pause() => _player.Pause(); /// /// Stop current playback /// public void Stop() => _player.Stop(); /// /// 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 /// /// TODO: The model shouldn't care about this /// public string GenerateDigitString() { // If the disc isn't initialized, return all '-' characters if(_player?.Initialized != true) return string.Empty.PadLeft(20, '-'); // Otherwise, take the current time into account ulong sectorTime = _player.GetCurrentSectorTime(); int[] numbers = new int[] { _player.CurrentTrackNumber + 1, _player.CurrentTrackIndex, (int)(sectorTime / (75 * 60)), (int)(sectorTime / 75 % 60), (int)(sectorTime % 75), _player.TotalTracks, _player.TotalIndexes, (int)(_player.TotalTime / (75 * 60)), (int)(_player.TotalTime / 75 % 60), (int)(_player.TotalTime % 75), }; return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2))); } /// /// Set de-emphasis status /// /// public void SetDeEmphasis(bool apply) => _player?.SetDeEmphasis(apply); /// /// Temporarily mute playback /// public void ToggleMute() { if(_lastVolume == null) { _lastVolume = Volume; Volume = 0; } else { Volume = _lastVolume.Value; _lastVolume = null; } } /// /// Update the UI from the internal player /// private void PlayerStateChanged(object sender, PropertyChangedEventArgs e) { if(_player?.Initialized != true) return; CurrentTrackNumber = _player.CurrentTrackNumber; CurrentTrackIndex = _player.CurrentTrackIndex; CurrentSector = _player.CurrentSector; HiddenTrack = _player.HiddenTrack; QuadChannel = _player.QuadChannel; IsDataTrack = _player.IsDataTrack; CopyAllowed = _player.CopyAllowed; TrackHasEmphasis = _player.TrackHasEmphasis; Playing = _player.Playing; ApplyDeEmphasis = _player.ApplyDeEmphasis; Volume = _player.Volume; } #endregion } }