diff --git a/Core/Bridge/TweetDeckBridge.cs b/Core/Bridge/TweetDeckBridge.cs
index de747ddc..ca141234 100644
--- a/Core/Bridge/TweetDeckBridge.cs
+++ b/Core/Bridge/TweetDeckBridge.cs
@@ -114,6 +114,10 @@ public void ScreenshotTweet(string html, int width, int height){
             form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
         }
 
+        public void PlayVideo(string url){
+            form.InvokeAsyncSafe(() => form.PlayVideo(url));
+        }
+
         public void FixClipboard(){
             form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
         }
diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs
index f0a74f2b..9027e562 100644
--- a/Core/FormBrowser.cs
+++ b/Core/FormBrowser.cs
@@ -12,6 +12,7 @@
 using TweetDuck.Core.Notification;
 using TweetDuck.Core.Notification.Screenshot;
 using TweetDuck.Core.Other;
+using TweetDuck.Core.Other.Media;
 using TweetDuck.Core.Other.Settings;
 using TweetDuck.Core.Utils;
 using TweetDuck.Plugins;
@@ -58,6 +59,7 @@ public bool IsWaiting{
 
         private TweetScreenshotManager notificationScreenshotManager;
         private SoundNotification soundNotification;
+        private VideoPlayer videoPlayer;
 
         public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
             InitializeComponent();
@@ -506,6 +508,19 @@ public void PlayNotificationSound(){
             soundNotification.Play(Config.NotificationSoundPath);
         }
 
+        public void PlayVideo(string url){
+            if (videoPlayer == null){
+                videoPlayer = new VideoPlayer(this);
+            }
+
+            if (!string.IsNullOrEmpty(url)){
+                videoPlayer.Launch(url);
+            }
+            else{
+                videoPlayer.Close();
+            }
+        }
+
         public void OnTweetScreenshotReady(string html, int width, int height){
             if (notificationScreenshotManager == null){
                 notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
diff --git a/Core/Other/Media/VideoPlayer.cs b/Core/Other/Media/VideoPlayer.cs
new file mode 100644
index 00000000..61fc5b88
--- /dev/null
+++ b/Core/Other/Media/VideoPlayer.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Windows.Forms;
+
+namespace TweetDuck.Core.Other.Media{
+    class VideoPlayer{
+        private readonly string PlayerExe = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe");
+
+        private readonly Form owner;
+        private Process currentProcess;
+
+        public VideoPlayer(Form owner){
+            this.owner = owner;
+        }
+
+        public void Launch(string url){
+            Close();
+
+            try{
+                if ((currentProcess = Process.Start(new ProcessStartInfo{
+                    FileName = PlayerExe,
+                    Arguments = $"{owner.Handle} \"{url}\"",
+                    UseShellExecute = false,
+                    RedirectStandardOutput = true
+                })) != null){
+                    currentProcess.EnableRaisingEvents = true;
+                    currentProcess.Exited += process_Exited;
+
+                    #if DEBUG
+                    currentProcess.BeginOutputReadLine();
+                    currentProcess.OutputDataReceived += (sender, args) => Debug.WriteLine("VideoPlayer: "+args.Data);
+                    #endif
+                }
+            }catch(Exception e){
+                Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
+            }
+        }
+
+        public void Close(){
+            if (currentProcess != null){
+                currentProcess.Exited -= process_Exited;
+
+                try{
+                    currentProcess.Kill();
+                }catch{
+                    // kill me instead then
+                }
+
+                currentProcess.Dispose();
+                currentProcess = null;
+            }
+        }
+
+        private void process_Exited(object sender, EventArgs e){
+            switch(currentProcess.ExitCode){
+                case 2: // CODE_LAUNCH_FAIL
+                    // TODO
+                    break;
+
+                case 3: // CODE_MEDIA_ERROR
+                    // TODO
+                    break;
+            }
+
+            currentProcess.Dispose();
+            currentProcess = null;
+        }
+    }
+}
diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js
index 7857e287..14c751b7 100644
--- a/Resources/Scripts/code.js
+++ b/Resources/Scripts/code.js
@@ -789,20 +789,52 @@
   }
   
   //
-  // Block: Setup unsupported video element hook.
+  // Block: Setup video player hooks.
   //
   (function(){
-    var cancelModal = false;
+    var playVideo = function(url){
+      $('<div class="ovl" style="display:block"></div>').click(function(){
+        $TD.playVideo(null);
+        $(this).remove();
+      }).appendTo(app);
+      
+      $TD.playVideo(url);
+    };
+    
+    app.delegate(".js-gif-play", "click", function(e){
+      let src = $(this).closest(".js-media-gif-container").find("video").attr("src");
+      
+      if (src){
+        playVideo(src);
+      }
+      else{
+        let parent = $(e.target).closest(".js-tweet").first();
+        let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
+        $TD.openBrowser(link.attr("href"));
+      }
+      
+      e.stopPropagation();
+    });
     
     if (!ensurePropertyExists(TD, "components", "MediaGallery", "prototype", "_loadTweet") ||
         !ensurePropertyExists(TD, "components", "BaseModal", "prototype", "setAndShowContainer") ||
         !ensurePropertyExists(TD, "ui", "Column", "prototype", "playGifIfNotManuallyPaused"))return;
     
+    var cancelModal = false;
+    
     TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
       let media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
 
       if (media && media.isVideo && media.service !== "youtube"){
-        $TD.openBrowser(this.clickedLink);
+        let data = media.chooseVideoVariant();
+        
+        if (data.content_type === "video/mp4"){
+          playVideo(data.url);
+        }
+        else{
+          $TD.openBrowser(this.clickedLink);
+        }
+        
         cancelModal = true;
       }
     });
@@ -816,14 +848,6 @@
     
     TD.ui.Column.prototype.playGifIfNotManuallyPaused = function(){};
     TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
-    
-    app.delegate(".js-gif-play", "click", function(e){
-      let parent = $(e.target).closest(".js-tweet").first();
-      let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
-      
-      $TD.openBrowser(link.attr("href"));
-      e.stopPropagation();
-    });
   })();
   
   //
diff --git a/TweetDuck.csproj b/TweetDuck.csproj
index 6eb8c2e8..06abcad5 100644
--- a/TweetDuck.csproj
+++ b/TweetDuck.csproj
@@ -142,6 +142,7 @@
     <Compile Include="Core\Other\FormPlugins.Designer.cs">
       <DependentUpon>FormPlugins.cs</DependentUpon>
     </Compile>
+    <Compile Include="Core\Other\Media\VideoPlayer.cs" />
     <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs">
       <SubType>Form</SubType>
     </Compile>
diff --git a/video/TweetDuck.Video/Program.cs b/video/TweetDuck.Video/Program.cs
index 02fefddb..5dc80c66 100644
--- a/video/TweetDuck.Video/Program.cs
+++ b/video/TweetDuck.Video/Program.cs
@@ -4,6 +4,7 @@
 
 namespace TweetDuck.Video{
     static class Program{
+        // referenced in VideoPlayer
         public const int CODE_INVALID_ARGS = 1;
         public const int CODE_LAUNCH_FAIL = 2;
         public const int CODE_MEDIA_ERROR = 3;