context: using System;
  using System.Diagnostics;
  using System.Globalization;
  using System.IO;
  using System.Linq;
  using System.Net.Sockets;
  using System.Text;
  using System.Threading;
  
  namespace ScriptPlayer.Shared
  {
      public class ZoomPlayerTimeSource : TimeSource, IDisposable, IOnScreenDisplay
      {
          public override string Name => "Zoom Player";
          public override bool ShowBanner => true;
          public override string ConnectInstructions => "Not connected.\r\nStart Zoom Player and enable External TCP Control (Advanced Mode - System).";
  
          private ZoomPlayerConnectionSettings _connectionSettings;
  
          private readonly Thread _clientLoop;
          private readonly ManualTimeSource _timeSource;
  
          private bool _running = true;
          private TcpClient _client;
          private TimeSpan _lastReceivedTimestamp = TimeSpan.MaxValue;
          private string _previousDesignation;
  
          public ZoomPlayerTimeSource(ISampleClock clock, ZoomPlayerConnectionSettings connectionSettings)
          {
              _connectionSettings = connectionSettings;
  
              _timeSource = new ManualTimeSource(clock, TimeSpan.FromMilliseconds(100));
              _timeSource.DurationChanged += TimeSourceOnDurationChanged;
              _timeSource.IsPlayingChanged += TimeSourceOnIsPlayingChanged;
              _timeSource.ProgressChanged += TimeSourceOnProgressChanged;
              _timeSource.PlaybackRateChanged += TimeSourceOnPlaybackRateChanged;
  
              _clientLoop = new Thread(ClientLoop);
              _clientLoop.Start();
          }
  
          private void TimeSourceOnPlaybackRateChanged(object sender, double d)
          {
              OnPlaybackRateChanged(d);
          }
  
          public void UpdateConnectionSettings(ZoomPlayerConnectionSettings connectionSettings)
          {
              _connectionSettings = connectionSettings;
          }
  
          private void TimeSourceOnProgressChanged(object sender, TimeSpan progress)
          {
              Progress = progress;
          }
  
          private void TimeSourceOnDurationChanged(object sender, TimeSpan duration)
          {
              Duration = duration;
          }
  
          private void TimeSourceOnIsPlayingChanged(object sender, bool isPlaying)
          {
              IsPlaying = isPlaying;
          }
  
          private void ClientLoop()
          {
              while (_running)
              {
                  try
                  {
                      _client = new TcpClient();
                      _client.Connect(_connectionSettings.ToEndpoint());
  
                      SetConnected(true);
  
                      SendCommand(ZoomPlayerCommandCodes.RequestPlayingFileName, "");
                      Thread.Sleep(1000);
                      SendCommand(ZoomPlayerCommandCodes.SendTimelineUpdate, "1");
                      Thread.Sleep(1000);
                      SendCommand(ZoomPlayerCommandCodes.SendTimelineUpdate, "2");
  
                      using (NetworkStream stream = _client.GetStream())
                      {
                          using (StreamReader reader = new StreamReader(stream))
                          {
                              while (!reader.EndOfStream)
                              {
                                  string line = reader.ReadLine();
                                  InterpretLine(line);
                              }
                          }
                      }
                  }
                  catch (ThreadAbortException)
                  {
                      return;
                  }
                  catch (ThreadInterruptedException)
                  {
                      return;
                  }
                  catch (Exception e)
                  {
                      Debug.WriteLine(e.Message);
                      Thread.Sleep(1000);
                  }
                  finally
                  {
                      DispatchPause();
                      _client.Dispose();
                      _client = null;
  
                      if (_running)
                          SetConnected(false);
                  }
              }
          }
  
          private void DispatchPause()
          {
              if (CheckAccess())
                  _timeSource.Pause();
              else
              {
                  try
                  {
                      Dispatcher.Invoke(DispatchPause);
                  }
                  catch
                  {
  
                  }
              }
          }
  
          private void SetConnected(bool isConnected)
          {
              if (CheckAccess())
                  IsConnected = isConnected;
              else
              {
                  try
                  {
                      Dispatcher.Invoke(() => { SetConnected(isConnected); });
                  }
                  catch
                  {
  
                  }
              }
          }
  
          private void InterpretLine(string line)
          {
              if (_timeSource.CheckAccess())
              {
                  //Debug.WriteLine(line);
  
                  ZoomPlayerMessageCodes commandCode = (ZoomPlayerMessageCodes)int.Parse(line.Substring(0, 4));
                  string parameter = line.Substring(4).Trim();
  
                  switch (commandCode)
                  {
                      case ZoomPlayerMessageCodes.StateChanged:
                          ZoomPlayerPlaybackStates state = (ZoomPlayerPlaybackStates)int.Parse(parameter);
                          if (state == ZoomPlayerPlaybackStates.Playing)
                              _timeSource.Play();
                          else
                              _timeSource.Pause();
  
                          break;
                      case ZoomPlayerMessageCodes.PositionUpdate:
                          string[] parts = parameter.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
                              .Select(s => s.Trim()).ToArray();
  
                          string[] timeFormats = { "hh\\:mm\\:ss", "mm\\:ss" };
  
                          TimeSpan position = TimeSpan.ParseExact(parts[0], timeFormats, CultureInfo.InvariantCulture);
                          TimeSpan duration = TimeSpan.ParseExact(parts[1], timeFormats, CultureInfo.InvariantCulture);
  
                          _timeSource.SetDuration(duration);
                          _timeSource.SetPosition(position);
                          break;
                      case ZoomPlayerMessageCodes.CurrentlyLoadedFile:
                          if (!String.IsNullOrWhiteSpace(parameter))
                              OnFileOpened(parameter);
                          break;
                  }
              }
              else
              {
                  _timeSource.Dispatcher.Invoke(() => InterpretLine(line));
              }
          }
  
          public override double PlaybackRate
          {
              get => _timeSource.PlaybackRate;
  
gt:             set => _timeSource.PlaybackRate = value;