aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Plugins.Tmt5/Tmt5MediaPlayer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Plugins.Tmt5/Tmt5MediaPlayer.cs')
-rw-r--r--MediaBrowser.Plugins.Tmt5/Tmt5MediaPlayer.cs399
1 files changed, 399 insertions, 0 deletions
diff --git a/MediaBrowser.Plugins.Tmt5/Tmt5MediaPlayer.cs b/MediaBrowser.Plugins.Tmt5/Tmt5MediaPlayer.cs
new file mode 100644
index 000000000..96b1a83d4
--- /dev/null
+++ b/MediaBrowser.Plugins.Tmt5/Tmt5MediaPlayer.cs
@@ -0,0 +1,399 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Model.DTO;
+using MediaBrowser.UI.Configuration;
+using MediaBrowser.UI.Playback;
+using MediaBrowser.UI.Playback.ExternalPlayer;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel.Composition;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Plugins.Tmt5
+{
+ /// <summary>
+ /// Class GenericExternalPlayer
+ /// </summary>
+ [Export(typeof(BaseMediaPlayer))]
+ public class Tmt5MediaPlayer : BaseExternalPlayer
+ {
+ /// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public override string Name
+ {
+ get { return "TMT5"; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance can pause.
+ /// </summary>
+ /// <value><c>true</c> if this instance can pause; otherwise, <c>false</c>.</value>
+ public override bool CanPause
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance can close automatically.
+ /// </summary>
+ /// <value><c>true</c> if this instance can close automatically; otherwise, <c>false</c>.</value>
+ protected override bool CanCloseAutomatically
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Gets the play state directory.
+ /// </summary>
+ /// <value>The play state directory.</value>
+ private string PlayStateDirectory
+ {
+ get
+ {
+ return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ArcSoft");
+ }
+ }
+
+
+ /// <summary>
+ /// The _current position ticks
+ /// </summary>
+ private long? _currentPositionTicks;
+
+ /// <summary>
+ /// Gets the current position ticks.
+ /// </summary>
+ /// <value>The current position ticks.</value>
+ public override long? CurrentPositionTicks
+ {
+ get
+ {
+ return _currentPositionTicks;
+ }
+ }
+
+ /// <summary>
+ /// The _current playlist index
+ /// </summary>
+ private int _currentPlaylistIndex;
+
+ /// <summary>
+ /// Gets the index of the current playlist.
+ /// </summary>
+ /// <value>The index of the current playlist.</value>
+ public override int CurrentPlaylistIndex
+ {
+ get
+ {
+ return _currentPlaylistIndex;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the status file watcher.
+ /// </summary>
+ /// <value>The status file watcher.</value>
+ private FileSystemWatcher StatusFileWatcher { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has started playing.
+ /// </summary>
+ /// <value><c>true</c> if this instance has started playing; otherwise, <c>false</c>.</value>
+ private bool HasStartedPlaying { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has stopped playing.
+ /// </summary>
+ /// <value><c>true</c> if this instance has stopped playing; otherwise, <c>false</c>.</value>
+ private bool HasStoppedPlaying { get; set; }
+
+ /// <summary>
+ /// Determines whether this instance can play the specified item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns><c>true</c> if this instance can play the specified item; otherwise, <c>false</c>.</returns>
+ public override bool CanPlay(DtoBaseItem item)
+ {
+ return item.IsVideo || item.IsAudio;
+ }
+
+ /// <summary>
+ /// Called when [player stopped internal].
+ /// </summary>
+ protected override void OnPlayerStoppedInternal()
+ {
+ DisposeFileSystemWatcher();
+ HasStartedPlaying = false;
+ HasStoppedPlaying = false;
+ _currentPlaylistIndex = 0;
+ _currentPositionTicks = 0;
+
+ base.OnPlayerStoppedInternal();
+ }
+
+ /// <summary>
+ /// Gets the command arguments.
+ /// </summary>
+ /// <param name="items">The items.</param>
+ /// <param name="options">The options.</param>
+ /// <param name="playerConfiguration">The player configuration.</param>
+ /// <returns>System.String.</returns>
+ protected override string GetCommandArguments(List<DtoBaseItem> items, PlayOptions options, PlayerConfiguration playerConfiguration)
+ {
+ return "\"" + items[0].Path + "\"";
+ }
+
+ /// <summary>
+ /// Called when [external player launched].
+ /// </summary>
+ protected override void OnExternalPlayerLaunched()
+ {
+ base.OnExternalPlayerLaunched();
+
+ // If the playstate directory exists, start watching it
+ if (Directory.Exists(PlayStateDirectory))
+ {
+ ReloadFileSystemWatcher();
+ }
+ }
+
+ /// <summary>
+ /// Pauses the internal.
+ /// </summary>
+ /// <returns>Task.</returns>
+ protected override Task PauseInternal()
+ {
+ return SendCommandToMMC("-pause");
+ }
+
+ /// <summary>
+ /// Uns the pause internal.
+ /// </summary>
+ /// <returns>Task.</returns>
+ protected override Task UnPauseInternal()
+ {
+ return SendCommandToMMC("-play");
+ }
+
+ /// <summary>
+ /// Stops the internal.
+ /// </summary>
+ /// <returns>Task.</returns>
+ protected override Task StopInternal()
+ {
+ return SendCommandToMMC("-stop");
+ }
+
+ /// <summary>
+ /// Closes the player.
+ /// </summary>
+ /// <returns>Task.</returns>
+ protected Task ClosePlayer()
+ {
+ return SendCommandToMMC("-close");
+ }
+
+ /// <summary>
+ /// Seeks the internal.
+ /// </summary>
+ /// <param name="positionTicks">The position ticks.</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.InvalidOperationException">No media to seek to</exception>
+ protected override Task SeekInternal(long positionTicks)
+ {
+ if (CurrentMedia == null)
+ {
+ throw new InvalidOperationException("No media to seek to");
+ }
+
+ if (CurrentMedia.Chapters == null)
+ {
+ throw new InvalidOperationException("TMT5 cannot seek without chapter information");
+ }
+
+ var chapterIndex = 0;
+
+ for (var i = 0; i < CurrentMedia.Chapters.Count; i++)
+ {
+ if (CurrentMedia.Chapters[i].StartPositionTicks < positionTicks)
+ {
+ chapterIndex = i;
+ }
+ }
+
+ return JumpToChapter(chapterIndex);
+ }
+
+ /// <summary>
+ /// Jumps to chapter.
+ /// </summary>
+ /// <param name="chapter">The chapter.</param>
+ /// <returns>Task.</returns>
+ protected Task JumpToChapter(int chapter)
+ {
+ return SendCommandToMMC(" -chapter " + chapter);
+ }
+
+ /// <summary>
+ /// Sends an arbitrary command to the TMT MMC console
+ /// </summary>
+ /// <param name="command">The command.</param>
+ /// <returns>Task.</returns>
+ protected Task SendCommandToMMC(string command)
+ {
+ return Task.Run(() =>
+ {
+ var directory = Path.GetDirectoryName(CurrentPlayerConfiguration.Command);
+
+ var processInfo = new ProcessStartInfo
+ {
+ FileName = Path.Combine(directory, "MMCEDT5.exe"),
+ Arguments = command,
+ CreateNoWindow = true
+ };
+
+ Logger.Debug("{0} {1}", processInfo.FileName, processInfo.Arguments);
+
+ using (var process = Process.Start(processInfo))
+ {
+ process.WaitForExit(2000);
+ }
+ });
+ }
+
+ /// <summary>
+ /// Reloads the file system watcher.
+ /// </summary>
+ private void ReloadFileSystemWatcher()
+ {
+ DisposeFileSystemWatcher();
+
+ Logger.Info("Watching TMT folder: " + PlayStateDirectory);
+
+ StatusFileWatcher = new FileSystemWatcher(PlayStateDirectory, "*.set")
+ {
+ IncludeSubdirectories = true
+ };
+
+ // Need to include subdirectories since there are subfolders undearneath this with the TMT version #.
+ StatusFileWatcher.Changed += StatusFileWatcher_Changed;
+ StatusFileWatcher.EnableRaisingEvents = true;
+ }
+
+ private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+ /// <summary>
+ /// Handles the Changed event of the StatusFileWatcher control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing the event data.</param>
+ async void StatusFileWatcher_Changed(object sender, FileSystemEventArgs e)
+ {
+ Logger.Debug("TMT File Watcher reports change type {1} at {0}", e.FullPath, e.ChangeType);
+
+ NameValueCollection values;
+
+ try
+ {
+ values = FileSystem.ParseIniFile(e.FullPath);
+ }
+ catch (IOException)
+ {
+ // This can happen if the file is being written to at the exact moment we're trying to access it
+ // Unfortunately we kind of have to just eat it
+ return;
+ }
+
+ var tmtPlayState = values["State"];
+
+ if (tmtPlayState.Equals("play", StringComparison.OrdinalIgnoreCase))
+ {
+ PlayState = PlayState.Playing;
+
+ // Playback just started
+ HasStartedPlaying = true;
+
+ if (CurrentPlayOptions.StartPositionTicks > 0)
+ {
+ SeekInternal(CurrentPlayOptions.StartPositionTicks);
+ }
+ }
+ else if (tmtPlayState.Equals("pause", StringComparison.OrdinalIgnoreCase))
+ {
+ PlayState = PlayState.Paused;
+ }
+
+ // If playback has previously started...
+ // First notify the Progress event handler
+ // Then check if playback has stopped
+ if (HasStartedPlaying)
+ {
+ TimeSpan currentPosition;
+
+ //TimeSpan.TryParse(values["TotalTime"], out currentDuration);
+
+ if (TimeSpan.TryParse(values["CurTime"], UsCulture, out currentPosition))
+ {
+ _currentPositionTicks = currentPosition.Ticks;
+ }
+
+ _currentPlaylistIndex = 0;
+
+ // Playback has stopped
+ if (tmtPlayState.Equals("stop", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Info("Playstate changed to stopped");
+
+ if (!HasStoppedPlaying)
+ {
+ HasStoppedPlaying = true;
+
+ DisposeFileSystemWatcher();
+
+ await ClosePlayer().ConfigureAwait(false);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Disposes the file system watcher.
+ /// </summary>
+ private void DisposeFileSystemWatcher()
+ {
+ if (StatusFileWatcher != null)
+ {
+ StatusFileWatcher.EnableRaisingEvents = false;
+ StatusFileWatcher.Changed -= StatusFileWatcher_Changed;
+ StatusFileWatcher.Dispose();
+ StatusFileWatcher = null;
+ }
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected override void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ DisposeFileSystemWatcher();
+ }
+
+ base.Dispose(dispose);
+ }
+ }
+}