From 2a3dca446771db5688eac311b1f178817f159471 Mon Sep 17 00:00:00 2001
From: chylex <contact@chylex.com>
Date: Sun, 13 Aug 2017 17:52:46 +0200
Subject: [PATCH] Rewrite video player to use duplex pipe for process
 communication

---
 Core/FormBrowser.cs                  | 10 ----
 Core/Other/Management/VideoPlayer.cs | 77 ++++++++++++++++++++++++----
 Program.cs                           |  2 -
 Resources/Scripts/code.js            |  1 -
 video/TweetDuck.Video/FormPlayer.cs  | 20 +++++++-
 video/TweetDuck.Video/Program.cs     |  7 ++-
 6 files changed, 88 insertions(+), 29 deletions(-)

diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs
index 00c930ab..8846fd1f 100644
--- a/Core/FormBrowser.cs
+++ b/Core/FormBrowser.cs
@@ -389,16 +389,6 @@ protected override void WndProc(ref Message m){
                         BrowserProcesses.Link(m.LParam.ToInt32(), processId);
                     }
 
-                    return;
-                }
-                else if (m.Msg == Program.VideoPlayerMessage){
-                    int volume = m.WParam.ToInt32();
-
-                    if (Handle == m.LParam && volume != Config.VideoPlayerVolume){
-                        Config.VideoPlayerVolume = volume;
-                        Config.Save();
-                    }
-
                     return;
                 }
             }
diff --git a/Core/Other/Management/VideoPlayer.cs b/Core/Other/Management/VideoPlayer.cs
index 616a77ae..6dfb0239 100644
--- a/Core/Other/Management/VideoPlayer.cs
+++ b/Core/Other/Management/VideoPlayer.cs
@@ -4,6 +4,7 @@
 using System.Windows.Forms;
 using TweetDuck.Core.Controls;
 using TweetDuck.Core.Utils;
+using TweetLib.Communication;
 
 namespace TweetDuck.Core.Other.Management{
     sealed class VideoPlayer : IDisposable{
@@ -23,23 +24,32 @@ public bool Running{
         public event EventHandler ProcessExited;
 
         private readonly Form owner;
-        private Process currentProcess;
         private string lastUrl;
 
+        private Process currentProcess;
+        private DuplexPipe.Server currentPipe;
+        private bool isClosing;
+
         public VideoPlayer(Form owner){
             this.owner = owner;
             this.owner.FormClosing += owner_FormClosing;
         }
 
         public void Launch(string url){
-            Close();
+            if (Running){
+                Destroy();
+                isClosing = false;
+            }
 
             lastUrl = url;
 
             try{
+                currentPipe = DuplexPipe.CreateServer();
+                currentPipe.DataIn += currentPipe_DataIn;
+
                 if ((currentProcess = Process.Start(new ProcessStartInfo{
                     FileName = PlayerExe,
-                    Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\"",
+                    Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"",
                     UseShellExecute = false,
                     RedirectStandardOutput = true
                 })) != null){
@@ -51,15 +61,61 @@ public void Launch(string url){
                     currentProcess.OutputDataReceived += (sender, args) => Debug.WriteLine("VideoPlayer: "+args.Data);
                     #endif
                 }
+
+                currentPipe.DisposeToken();
             }catch(Exception e){
                 Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
             }
         }
 
+        private void currentPipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
+            owner.InvokeSafe(() => {
+                switch(e.Key){
+                    case "vol":
+                        if (int.TryParse(e.Data, out int volume) && volume != Program.UserConfig.VideoPlayerVolume){
+                            Program.UserConfig.VideoPlayerVolume = volume;
+                            Program.UserConfig.Save();
+                        }
+
+                        break;
+
+                    case "rip":
+                        currentPipe.Dispose();
+                        currentPipe = null;
+                    
+                        currentProcess.Dispose();
+                        currentProcess = null;
+
+                        isClosing = false;
+                        TriggerProcessExitEventUnsafe();
+                        break;
+                }
+            });
+        }
+
         public void Close(){
             if (currentProcess != null){
-                currentProcess.Exited -= process_Exited;
+                if (isClosing){
+                    Destroy();
+                    isClosing = false;
+                }
+                else{
+                    isClosing = true;
+                    currentProcess.Exited -= process_Exited;
+                    currentPipe.Write("die");
+                }
+            }
+        }
 
+        public void Dispose(){
+            ProcessExited = null;
+
+            isClosing = true;
+            Destroy();
+        }
+
+        private void Destroy(){
+            if (currentProcess != null){
                 try{
                     currentProcess.Kill();
                 }catch{
@@ -68,16 +124,14 @@ public void Close(){
 
                 currentProcess.Dispose();
                 currentProcess = null;
+                
+                currentPipe.Dispose();
+                currentPipe = null;
 
-                owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
+                TriggerProcessExitEventUnsafe();
             }
         }
 
-        public void Dispose(){
-            ProcessExited = null;
-            Close();
-        }
-
         private void owner_FormClosing(object sender, FormClosingEventArgs e){
             if (currentProcess != null){
                 currentProcess.Exited -= process_Exited;
@@ -103,6 +157,9 @@ private void process_Exited(object sender, EventArgs e){
 
             currentProcess.Dispose();
             currentProcess = null;
+
+            currentPipe.Dispose();
+            currentPipe = null;
             
             owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
         }
diff --git a/Program.cs b/Program.cs
index 7fb24971..3775e772 100644
--- a/Program.cs
+++ b/Program.cs
@@ -45,7 +45,6 @@ static class Program{
 
         public static uint WindowRestoreMessage;
         public static uint SubProcessMessage;
-        public static uint VideoPlayerMessage;
 
         private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
         private static bool HasCleanedUp;
@@ -77,7 +76,6 @@ private static void Main(){
             
             WindowRestoreMessage = Comms.RegisterMessage("TweetDuckRestore");
             SubProcessMessage = Comms.RegisterMessage("TweetDuckSubProcess");
-            VideoPlayerMessage = Comms.RegisterMessage("TweetDuckVideoPlayer");
 
             if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
                 FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js
index 328aa9c5..2a5ce9ff 100644
--- a/Resources/Scripts/code.js
+++ b/Resources/Scripts/code.js
@@ -797,7 +797,6 @@
     var playVideo = function(url){
       $('<div id="td-video-player-overlay" class="ovl" style="display:block"></div>').on("click contextmenu", function(){
         $TD.playVideo(null);
-        $(this).remove();
       }).appendTo(app);
       
       $TD.playVideo(url);
diff --git a/video/TweetDuck.Video/FormPlayer.cs b/video/TweetDuck.Video/FormPlayer.cs
index da7103a3..168343fa 100644
--- a/video/TweetDuck.Video/FormPlayer.cs
+++ b/video/TweetDuck.Video/FormPlayer.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Drawing;
+using System.Globalization;
 using System.Runtime.InteropServices;
 using System.Windows.Forms;
 using TweetDuck.Video.Controls;
@@ -12,6 +13,7 @@ partial class FormPlayer : Form{
 
         private readonly IntPtr ownerHandle;
         private readonly string videoUrl;
+        private readonly DuplexPipe pipe;
         
         private readonly ControlWMP player;
         private bool wasCursorInside;
@@ -20,11 +22,13 @@ partial class FormPlayer : Form{
 
         private WindowsMediaPlayer Player => player.Ocx;
 
-        public FormPlayer(IntPtr handle, int volume, string url){
+        public FormPlayer(IntPtr handle, int volume, string url, string token){
             InitializeComponent();
 
             this.ownerHandle = handle;
             this.videoUrl = url;
+            this.pipe = DuplexPipe.CreateClient(token);
+            this.pipe.DataIn += pipe_DataIn;
             
             player = new ControlWMP{
                 Dock = DockStyle.Fill
@@ -54,6 +58,18 @@ private void FormPlayer_Load(object sender, EventArgs e){
             Player.URL = videoUrl;
         }
 
+        private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
+            switch(e.Key){
+                case "die":
+                    timerSync.Stop();
+                    Visible = false;
+                    pipe.Write("rip");
+
+                    Close();
+                    break;
+            }
+        }
+
         private void player_PlayStateChange(int newState){
             WMPPlayState state = (WMPPlayState)newState;
 
@@ -134,7 +150,7 @@ private void timerSync_Tick(object sender, EventArgs e){
 
         private void timerData_Tick(object sender, EventArgs e){
             timerData.Stop();
-            Comms.BroadcastMessage(Program.VideoPlayerMessage, new UIntPtr((uint)trackBarVolume.Value), ownerHandle);
+            pipe.Write("vol", trackBarVolume.Value.ToString(CultureInfo.InvariantCulture));
         }
 
         private void progressSeek_MouseDown(object sender, MouseEventArgs e){
diff --git a/video/TweetDuck.Video/Program.cs b/video/TweetDuck.Video/Program.cs
index 4e1de6f7..8f514203 100644
--- a/video/TweetDuck.Video/Program.cs
+++ b/video/TweetDuck.Video/Program.cs
@@ -13,9 +13,6 @@ static class Program{
         public const int CODE_OWNER_GONE = 5;
         public const int CODE_USER_REQUESTED = 6;
 
-        private static uint? message;
-        public static uint VideoPlayerMessage => message ?? (message = Comms.RegisterMessage("TweetDuckVideoPlayer")).Value;
-
         [STAThread]
         private static int Main(string[] args){
             Application.EnableVisualStyles();
@@ -24,17 +21,19 @@ private static int Main(string[] args){
             IntPtr ownerHandle;
             int defaultVolume;
             string videoUrl;
+            string pipeToken;
 
             try{
                 ownerHandle = new IntPtr(int.Parse(args[0], NumberStyles.Integer, CultureInfo.InvariantCulture));
                 defaultVolume = int.Parse(args[1], NumberStyles.Integer, CultureInfo.InvariantCulture);
                 videoUrl = new Uri(args[2], UriKind.Absolute).AbsoluteUri;
+                pipeToken = args[3];
             }catch{
                 return CODE_INVALID_ARGS;
             }
 
             try{
-                Application.Run(new FormPlayer(ownerHandle, defaultVolume, videoUrl));
+                Application.Run(new FormPlayer(ownerHandle, defaultVolume, videoUrl, pipeToken));
             }catch(Exception e){
                 Console.Out.WriteLine(e.Message);
                 return CODE_LAUNCH_FAIL;