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 => _initialized; private set => this.RaiseAndSetIfChanged(ref _initialized, value); } private bool _initialized; #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 track session /// public ushort CurrentTrackSession { get => _currentTrackSession; private set => this.RaiseAndSetIfChanged(ref _currentTrackSession, 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 ?? 0; /// /// Represents the total indices on the disc /// public int TotalIndexes => _opticalDisc?.TotalIndexes ?? 0; /// /// Total sectors in the image /// public ulong TotalSectors => _opticalDisc?.TotalSectors ?? 0; /// /// Represents the time adjustment offset for the disc /// public ulong TimeOffset => _opticalDisc?.TimeOffset ?? 0; /// /// Represents the total playing time for the disc /// public ulong TotalTime => _opticalDisc?.TotalTime ?? 0; private int _currentTrackNumber; private ushort _currentTrackIndex; private ushort _currentTrackSession; 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 /// /// Indicates the current player state /// public PlayerState PlayerState { get => _playerState; private set => this.RaiseAndSetIfChanged(ref _playerState, value); } /// /// Indicates how to handle playback of data tracks /// public DataPlayback DataPlayback { get => _dataPlayback; private set => this.RaiseAndSetIfChanged(ref _dataPlayback, value); } /// /// Indicates the repeat mode /// public RepeatMode RepeatMode { get => _repeatMode; private set => this.RaiseAndSetIfChanged(ref _repeatMode, 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 PlayerState _playerState; private DataPlayback _dataPlayback; private RepeatMode _repeatMode; private bool _applyDeEmphasis; private int _volume; #endregion #region Private State Variables /// /// Sound output handling class /// private readonly SoundOutput _soundOutput; /// /// OpticalDisc object /// private OpticalDiscBase _opticalDisc; /// /// Last volume for mute toggling /// private int? _lastVolume = null; #endregion /// /// Constructor /// /// Default volume between 0 and 100 to use when starting playback public Player(int defaultVolume) { Initialized = false; _soundOutput = new SoundOutput(defaultVolume); _soundOutput.SetDeEmphasis(false); } /// /// Initializes player from a given image path /// /// Path to the disc image /// Options to pass to the optical disc factory /// RepeatMode for sound output /// True if playback should begin immediately, false otherwise public void Init(string path, OpticalDiscOptions options, RepeatMode repeatMode, bool autoPlay) { // Reset initialization Initialized = false; // Initalize the disc _opticalDisc = OpticalDiscFactory.GenerateFromPath(path, options, autoPlay); if(_opticalDisc == null || !_opticalDisc.Initialized) return; // Add event handling for the optical disc _opticalDisc.PropertyChanged += OpticalDiscStateChanged; // Initialize the sound output _soundOutput.Init(_opticalDisc, repeatMode, autoPlay); 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.PlayerState != PlayerState.Paused && _soundOutput.PlayerState != PlayerState.Stopped) return; _soundOutput.Play(); _opticalDisc.SetTotalIndexes(); PlayerState = PlayerState.Playing; } /// /// Pause current playback /// public void Pause() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; else if(_soundOutput == null) return; else if(_soundOutput.PlayerState != PlayerState.Playing) return; _soundOutput?.Pause(); PlayerState = PlayerState.Paused; } /// /// Toggle current playback /// public void TogglePlayback() { switch(PlayerState) { case PlayerState.NoDisc: break; case PlayerState.Stopped: Play(); break; case PlayerState.Paused: Play(); break; case PlayerState.Playing: Pause(); break; } } /// /// Stop current playback /// public void Stop() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; else if(_soundOutput == null) return; else if(_soundOutput.PlayerState != PlayerState.Playing && _soundOutput.PlayerState != PlayerState.Paused) return; _soundOutput.Stop(); _opticalDisc.LoadFirstTrack(); PlayerState = PlayerState.Stopped; } /// /// Eject the currently loaded disc /// public void Eject() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; else if(_soundOutput == null) return; Stop(); _soundOutput.Eject(); _opticalDisc = null; PlayerState = PlayerState.NoDisc; Initialized = false; } /// /// Move to the next playable track /// public void NextTrack() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); _opticalDisc.NextTrack(); if(_opticalDisc is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) Play(); } /// /// Move to the previous playable track /// public void PreviousTrack() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); _opticalDisc.PreviousTrack(); if(_opticalDisc is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) 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; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); _opticalDisc.NextIndex(changeTrack); if(_opticalDisc is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) 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; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); _opticalDisc.PreviousIndex(changeTrack); if(_opticalDisc is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) Play(); } /// /// Fast-forward playback by 75 sectors /// public void FastForward() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; _opticalDisc.SetCurrentSector(_opticalDisc.CurrentSector + 75); } /// /// Rewind playback by 75 sectors /// public void Rewind() { if(_opticalDisc == null || !_opticalDisc.Initialized) return; _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 /// /// Extract a single track from the image to WAV /// /// /// Output path to write data to _opticalDisc?.ExtractTrackToWav(trackNumber, outputDirectory); /// /// Extract all tracks from the image to WAV /// Output path to write data to _opticalDisc?.ExtractAllTracksToWav(outputDirectory); /// /// Set data playback method [CompactDisc only] /// /// New playback value public void SetDataPlayback(DataPlayback dataPlayback) { if(_opticalDisc is CompactDisc compactDisc) compactDisc.DataPlayback = dataPlayback; } /// /// 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; } /// /// Set repeat mode /// /// New repeat mode value public void SetRepeatMode(RepeatMode repeatMode) => _soundOutput?.SetRepeatMode(repeatMode); /// /// Set the value for session handling [CompactDisc only] /// /// New session handling value public void SetSessionHandling(SessionHandling sessionHandling) { if(_opticalDisc is CompactDisc compactDisc) compactDisc.SessionHandling = sessionHandling; } /// /// 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) { PlayerState = _soundOutput.PlayerState; RepeatMode = _soundOutput.RepeatMode; ApplyDeEmphasis = _soundOutput.ApplyDeEmphasis; Volume = _soundOutput.Volume; } #endregion } }