using System; using System.ComponentModel; using Aaru.CommonTypes.Enums; using ReactiveUI; using RedBookPlayer.Models.Discs; using RedBookPlayer.Models.Factories; namespace RedBookPlayer.Models.Hardware { public class Player : ReactiveObject { /// /// Indicate if the player is ready to be used /// public bool Initialized { get; private set; } = false; #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 the sector starting the section /// public ulong SectionStartSector { get => _sectionStartSector; protected set => this.RaiseAndSetIfChanged(ref _sectionStartSector, 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 => _opticalDisc.TotalTracks; /// /// Represents the total indices on the disc /// public int TotalIndexes => _opticalDisc.TotalIndexes; /// /// Total sectors in the image /// public ulong TotalSectors => _opticalDisc.TotalSectors; /// /// Represents the time adjustment offset for the disc /// public ulong TimeOffset => _opticalDisc.TimeOffset; /// /// Represents the total playing time for the disc /// public ulong TotalTime => _opticalDisc.TotalTime; private int _currentTrackNumber; private ushort _currentTrackIndex; private ulong _currentSector; private ulong _sectionStartSector; private bool _hasHiddenTrack; private bool _quadChannel; private bool _isDataTrack; private bool _copyAllowed; private bool _trackHasEmphasis; #endregion #region SoundOutput Passthrough /// /// 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; private set => this.RaiseAndSetIfChanged(ref _volume, value); } private bool? _playing; private bool _applyDeEmphasis; private int _volume; #endregion #region Private State Variables /// /// Sound output handling class /// private readonly SoundOutput _soundOutput; /// /// OpticalDisc object /// private readonly OpticalDiscBase _opticalDisc; /// /// Last volume for mute toggling /// private int? _lastVolume = null; #endregion /// /// Create a new Player from a given image path /// /// Path to the disc image /// Generate a TOC if the disc is missing one [CompactDisc only] /// Load hidden tracks for playback [CompactDisc only] /// Load data tracks for playback [CompactDisc only] /// True if playback should begin immediately, false otherwise /// Default volume between 0 and 100 to use when starting playback public Player(string path, bool generateMissingToc, bool loadHiddenTracks, bool loadDataTracks, bool autoPlay, int defaultVolume) { // Set the internal state for initialization Initialized = false; _soundOutput = new SoundOutput(); _soundOutput.SetDeEmphasis(false); // Initalize the disc _opticalDisc = OpticalDiscFactory.GenerateFromPath(path, generateMissingToc, loadHiddenTracks, loadDataTracks, autoPlay); if(_opticalDisc == null || !_opticalDisc.Initialized) return; // Add event handling for the optical disc _opticalDisc.PropertyChanged += OpticalDiscStateChanged; // Initialize the sound output _soundOutput.Init(_opticalDisc, autoPlay, defaultVolume); if(_soundOutput == null || !_soundOutput.Initialized) return; // Add event handling for the sound output _soundOutput.PropertyChanged += SoundOutputStateChanged; // Mark the player as ready Initialized = true; // Force a refresh of the state information OpticalDiscStateChanged(this, null); SoundOutputStateChanged(this, null); } #region Playback /// /// Begin playback /// public void Play() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; else if(_soundOutput == null) return; else if(_soundOutput.Playing) return; _soundOutput.Play(); _opticalDisc.SetTotalIndexes(); Playing = true; } /// /// Pause current playback /// public void Pause() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; else if(_soundOutput == null) return; else if(!_soundOutput.Playing) return; _soundOutput?.Stop(); Playing = false; } /// /// Toggle current playback /// public void TogglePlayback() { if(Playing == true) Pause(); else Play(); } /// /// Stop current playback /// public void Stop() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; else if(_soundOutput == null) return; else if(!_soundOutput.Playing) return; _soundOutput?.Stop(); _opticalDisc.LoadFirstTrack(); Playing = null; } /// /// Move to the next playable track /// public void NextTrack() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; bool? wasPlaying = Playing; if(wasPlaying == true) Pause(); _opticalDisc.NextTrack(); if(_opticalDisc is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == true) Play(); } /// /// Move to the previous playable track /// public void PreviousTrack() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; bool? wasPlaying = Playing; if(wasPlaying == true) Pause(); _opticalDisc.PreviousTrack(); if(_opticalDisc is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == true) Play(); } /// /// Move to the next index /// /// True if index changes can trigger a track change, false otherwise public void NextIndex(bool changeTrack) { if(_opticalDisc == null || !_opticalDisc.Initialized) return; bool? wasPlaying = Playing; if(wasPlaying == true) Pause(); _opticalDisc.NextIndex(changeTrack); if(_opticalDisc is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == true) Play(); } /// /// Move to the previous index /// /// True if index changes can trigger a track change, false otherwise public void PreviousIndex(bool changeTrack) { if(_opticalDisc == null || !_opticalDisc.Initialized) return; bool? wasPlaying = Playing; if(wasPlaying == true) Pause(); _opticalDisc.PreviousIndex(changeTrack); if(_opticalDisc is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == true) Play(); } /// /// Fast-forward playback by 75 sectors, if possible /// public void FastForward() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; _opticalDisc.SetCurrentSector(Math.Min(_opticalDisc.TotalSectors, _opticalDisc.CurrentSector + 75)); } /// /// Rewind playback by 75 sectors, if possible /// public void Rewind() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; if(_opticalDisc.CurrentSector >= 75) _opticalDisc.SetCurrentSector(_opticalDisc.CurrentSector - 75); } #endregion #region Volume /// /// Increment the volume value /// public void VolumeUp() => SetVolume(Volume + 1); /// /// Decrement the volume value /// public void VolumeDown() => SetVolume(Volume + 1); /// /// Set the value for the volume /// /// New volume value public void SetVolume(int volume) => _soundOutput?.SetVolume(volume); /// /// Temporarily mute playback /// public void ToggleMute() { if(_lastVolume == null) { _lastVolume = Volume; SetVolume(0); } else { SetVolume(_lastVolume.Value); _lastVolume = null; } } #endregion #region Emphasis /// /// Enable de-emphasis /// public void EnableDeEmphasis() => SetDeEmphasis(true); /// /// Disable de-emphasis /// public void DisableDeEmphasis() => SetDeEmphasis(false); /// /// Toggle de-emphasis /// public void ToggleDeEmphasis() => SetDeEmphasis(!ApplyDeEmphasis); /// /// Set de-emphasis status /// /// private void SetDeEmphasis(bool apply) => _soundOutput?.SetDeEmphasis(apply); #endregion #region Helpers /// /// Set the value for loading data tracks [CompactDisc only] /// /// True to enable loading data tracks, false otherwise public void SetLoadDataTracks(bool load) { if(_opticalDisc is CompactDisc compactDisc) compactDisc.LoadDataTracks = load; } /// /// Set the value for loading hidden tracks [CompactDisc only] /// /// True to enable loading hidden tracks, false otherwise public void SetLoadHiddenTracks(bool load) { if(_opticalDisc is CompactDisc compactDisc) compactDisc.LoadHiddenTracks = load; } /// /// Update the player from the current OpticalDisc /// private void OpticalDiscStateChanged(object sender, PropertyChangedEventArgs e) { CurrentTrackNumber = _opticalDisc.CurrentTrackNumber; CurrentTrackIndex = _opticalDisc.CurrentTrackIndex; CurrentSector = _opticalDisc.CurrentSector; SectionStartSector = _opticalDisc.SectionStartSector; HiddenTrack = TimeOffset > 150; if(_opticalDisc is CompactDisc compactDisc) { QuadChannel = compactDisc.QuadChannel; IsDataTrack = compactDisc.IsDataTrack; CopyAllowed = compactDisc.CopyAllowed; TrackHasEmphasis = compactDisc.TrackHasEmphasis; } else { QuadChannel = false; IsDataTrack = _opticalDisc.TrackType != TrackType.Audio; CopyAllowed = false; TrackHasEmphasis = false; } } /// /// Update the player from the current SoundOutput /// private void SoundOutputStateChanged(object sender, PropertyChangedEventArgs e) { Playing = _soundOutput.Playing; ApplyDeEmphasis = _soundOutput.ApplyDeEmphasis; Volume = _soundOutput.Volume; } #endregion } }