using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.BL.Entities; using Tango.Core; using Tango.Logging; using Tango.PMR.Diagnostics; using Tango.Serialization; namespace Tango.Integration.Diagnostics { /// /// Represents a Tango machine diagnostics file player capable of streaming diagnostics data frames. /// /// /// public class DiagnosticsFilePlayer : ExtendedObject, IDisposable { private FileStream _dataFileStream; //Holds the data file stream. private DiagnosticsTimeCodeChannel _timeCodeChannel; //Holds the encapsulated time code data. private long _diagnosticsDataOffset; //Holds the actual starting position for the diagnostics packets. private Thread _playThread; //Holds the playing thread. private TaskCompletionSource _stopTaskSource; //Holds the "Stop" async method completion source. #region Events /// /// Occurs when there is a new diagnostic frame is available. /// public event EventHandler FrameReceived; #endregion #region Properties private bool _isLoaded; /// /// Gets a value indicating whether a diagnostics file is currently loaded and ready to be played. /// public bool IsLoaded { get { return _isLoaded; } private set { _isLoaded = value; RaisePropertyChangedAuto(); } } private bool _isPlaying; /// /// Gets a value indicating whether the player is in play mode (Played/Pause). /// public bool IsPlaying { get { return _isPlaying; } private set { _isPlaying = value; RaisePropertyChangedAuto(); } } private bool _isPaused; /// /// Gets or sets a value indicating whether the player is paused. /// public bool IsPaused { get { return _isPaused; } private set { _isPaused = value; RaisePropertyChangedAuto(); } } private TimeSpan _currentTime; /// /// Gets the current playing time. /// public TimeSpan CurrentTime { get { return _currentTime; } private set { _currentTime = value; RaisePropertyChanged(nameof(CurrentTime)); } } private TimeSpan _totalTime; /// /// Gets or sets the total playing time. /// public TimeSpan TotalTime { get { return _totalTime; } set { _totalTime = value; RaisePropertyChangedAuto(); } } private int _currentFrame; /// /// Gets or sets the current frame index. /// public int CurrentFrame { get { return _currentFrame; } set { _currentFrame = value; OnCurrentFrameChanged(); RaisePropertyChanged(nameof(CurrentFrame)); } } private long _totalFrames; /// /// Gets the total frames count. /// public long TotalFrames { get { return _totalFrames; } private set { _totalFrames = value; RaisePropertyChangedAuto(); } } private List _machineEvents; /// /// Gets or sets the machine events. /// public List MachineEvents { get { return _machineEvents; } set { _machineEvents = value; RaisePropertyChangedAuto(); } } private double _speed; /// /// Gets or sets the player speed (default 1.0). /// public double Speed { get { return _speed; } set { _speed = value; RaisePropertyChangedAuto(); } } #endregion #region Constructors /// /// Initializes a new instance of the class. /// public DiagnosticsFilePlayer() { Speed = 1.0; } #endregion #region Public Methods /// /// Loads the specified diagnostics file. /// /// Absolute file path. /// public async Task Load(String fileName) { Task task = new Task(() => { try { if (_dataFileStream != null) { _dataFileStream.Dispose(); } _dataFileStream = new FileStream(fileName, FileMode.Open); BinaryReader binaryReader = new BinaryReader(_dataFileStream); int timeCodeDataSize = binaryReader.ReadInt32(); byte[] timeCodeData = binaryReader.ReadBytes(timeCodeDataSize); BinaryDataSerializer serializer = new BinaryDataSerializer(); _timeCodeChannel = serializer.DeserializeFromBytes(timeCodeData); _diagnosticsDataOffset = _dataFileStream.Position; CurrentFrame = 0; TotalFrames = _timeCodeChannel.Frames.Count; TotalTime = TimeSpan.FromMilliseconds(_timeCodeChannel.Frames.Last().Milliseconds); if (_timeCodeChannel.Events != null) { MachineEvents = _timeCodeChannel.Events.Select(x => x.ToMachineEvent()).ToList(); } IsLoaded = true; } catch (Exception ex) { if (_dataFileStream != null) { _dataFileStream.Dispose(); } throw LogManager.Log(ex); } }); task.Start(); await task; } /// /// Seeks to the specified frame index. /// /// Index of the frame. public void Seek(int frameIndex) { if (frameIndex < 0) { frameIndex = 0; } else if (frameIndex > TotalFrames - 1) { frameIndex = (int)(TotalFrames - 1); } CurrentFrame = frameIndex; } /// /// Starts playing the diagnostics file. /// /// No diagnostics file is currently loaded. public void Play() { if (!IsLoaded) throw LogManager.Log(new InvalidOperationException("No diagnostics file is currently loaded.")); IsPaused = false; if (!IsPlaying) { IsPlaying = true; _playThread = new Thread(PlayThreadMethod); _playThread.IsBackground = true; _playThread.Start(); } } /// /// Stops this instance. /// /// /// No diagnostics file is currently loaded. public Task Stop() { if (!IsLoaded) throw LogManager.Log(new InvalidOperationException("No diagnostics file is currently loaded.")); if (IsPlaying) { _stopTaskSource = new TaskCompletionSource(); IsPlaying = false; IsPaused = false; return _stopTaskSource.Task; } else { return Task.FromResult(new object()); } } /// /// Pauses the player. /// /// No diagnostics file is currently loaded. public void Pause() { if (!IsLoaded) throw LogManager.Log(new InvalidOperationException("No diagnostics file is currently loaded.")); if (IsPlaying) { IsPaused = true; } } #endregion #region Protected Methods /// /// Called when the current frame has been changed /// protected void OnCurrentFrameChanged() { if (IsPlaying) { if (_currentFrame > _timeCodeChannel.Frames.Count - 1) { _currentFrame = _timeCodeChannel.Frames.Count - 1; } if (_dataFileStream != null && _dataFileStream.CanSeek) { _dataFileStream.Position = _diagnosticsDataOffset + _timeCodeChannel.Frames[_currentFrame].Position; } byte[] data = new byte[_timeCodeChannel.Frames[_currentFrame].FrameLength]; _dataFileStream.Read(data, 0, data.Length); DataFileFrame frame = DataFileFrame.Parser.ParseFrom(data); OnFrameReceived(frame); data = null; } if (_timeCodeChannel != null) { CurrentTime = TimeSpan.FromMilliseconds(_timeCodeChannel.Frames[_currentFrame].Milliseconds); } } /// /// Raises the event. /// /// The frame. protected virtual void OnFrameReceived(DataFileFrame frame) { FrameReceived?.Invoke(this, frame); } #endregion #region Playing Thread /// /// Handles the playing thread. /// private void PlayThreadMethod() { while (IsPlaying) { if (!IsPaused) { CurrentFrame++; if (CurrentFrame >= TotalFrames - 1) { CurrentFrame = 0; } } if (CurrentFrame > 0) { double sleep = _timeCodeChannel.Frames[CurrentFrame].Milliseconds - _timeCodeChannel.Frames[CurrentFrame - 1].Milliseconds; Thread.Sleep((int)(sleep / Speed)); } else { Thread.Sleep((int)(10d / Speed)); } } CurrentFrame = 0; CurrentTime = TimeSpan.Zero; _stopTaskSource.SetResult(new object()); } #endregion #region IDisposable /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { IsPlaying = false; if (_dataFileStream != null) { _dataFileStream.Dispose(); _dataFileStream = null; } } #endregion #region Finalizer /// /// Finalizes an instance of the class. /// ~DiagnosticsFilePlayer() { if (_dataFileStream != null) { _dataFileStream.Dispose(); } } #endregion } }