From 8b676fe6ce61661799fcee2dbeb452efebf1485d Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 11 Aug 2017 11:56:19 +0200 Subject: [PATCH] Implement video player in TweetDeck --- Core/Bridge/TweetDeckBridge.cs | 4 ++ Core/FormBrowser.cs | 15 +++++++ Core/Other/Media/VideoPlayer.cs | 70 ++++++++++++++++++++++++++++++++ Resources/Scripts/code.js | 46 ++++++++++++++++----- TweetDuck.csproj | 1 + video/TweetDuck.Video/Program.cs | 1 + 6 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 Core/Other/Media/VideoPlayer.cs 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;