From bd92fc6ee070424b3340ffe9be1ce83878b9b3c0 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Sat, 13 Jan 2018 22:59:34 +0100 Subject: [PATCH] Use <audio> for custom sound notifications & allow volume control for default one Closes #195 --- Configuration/UserConfig.cs | 13 +++- Core/Bridge/PropertyBridge.cs | 1 - Core/Bridge/TweetDeckBridge.cs | 2 +- Core/FormBrowser.cs | 37 ++--------- Core/Notification/SoundNotification.cs | 61 ++++++++++++------- Core/Other/FormSettings.cs | 2 +- .../Settings/TabSettingsSounds.Designer.cs | 7 +++ Core/Other/Settings/TabSettingsSounds.cs | 53 ++++++++-------- Core/TweetDeckBrowser.cs | 31 ++++++++++ Resources/Scripts/code.js | 27 +++++++- TweetDuck.csproj | 4 -- 11 files changed, 149 insertions(+), 89 deletions(-) diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index 0fc8b38a..6c982d3a 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -83,8 +83,8 @@ static UserConfig(){ public Size CustomNotificationSize { get; set; } = Size.Empty; public int NotificationScrollSpeed { get; set; } = 100; - public int NotificationSoundVolume { get; set; } = 100; private string _notificationSoundPath; + private int _notificationSoundVolume = 100; public string CustomCefArgs { get; set; } = null; public string CustomBrowserCSS { get; set; } = null; @@ -94,12 +94,18 @@ static UserConfig(){ public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation; public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty; + public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty; public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default; public string NotificationSoundPath{ - get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath; - set => _notificationSoundPath = value; + get => _notificationSoundPath ?? string.Empty; + set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged); + } + + public int NotificationSoundVolume{ + get => _notificationSoundVolume; + set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged); } public bool MuteNotifications{ @@ -122,6 +128,7 @@ public TrayIcon.Behavior TrayBehavior{ public event EventHandler MuteToggled; public event EventHandler ZoomLevelChanged; public event EventHandler TrayBehaviorChanged; + public event EventHandler SoundNotificationChanged; // END OF CONFIG diff --git a/Core/Bridge/PropertyBridge.cs b/Core/Bridge/PropertyBridge.cs index e43800c2..cfcbafdf 100644 --- a/Core/Bridge/PropertyBridge.cs +++ b/Core/Bridge/PropertyBridge.cs @@ -19,7 +19,6 @@ public static string GenerateScript(Environment environment){ build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn)); build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(Program.UserConfig.KeepLikeFollowDialogsOpen)); build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications)); - build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0)); build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews)); build.Append("x.translationTarget=").Append(Str(Program.UserConfig.TranslationTarget)); } diff --git a/Core/Bridge/TweetDeckBridge.cs b/Core/Bridge/TweetDeckBridge.cs index aa376266..48b95893 100644 --- a/Core/Bridge/TweetDeckBridge.cs +++ b/Core/Bridge/TweetDeckBridge.cs @@ -126,7 +126,7 @@ public void OnTweetPopup(string columnId, string chirpId, string columnName, str public void OnTweetSound(){ form.InvokeAsyncSafe(() => { form.OnTweetNotification(); - form.PlayNotificationSound(); + form.OnTweetSound(); }); } diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index ed964d6c..0e437218 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -15,7 +15,6 @@ using TweetDuck.Plugins; using TweetDuck.Plugins.Events; using TweetDuck.Updates; -using TweetLib.Audio; namespace TweetDuck.Core{ sealed partial class FormBrowser : Form{ @@ -50,7 +49,6 @@ public bool IsWaiting{ private FormWindowState prevState; private TweetScreenshotManager notificationScreenshotManager; - private SoundNotification soundNotification; private VideoPlayer videoPlayer; private AnalyticsManager analytics; @@ -82,7 +80,6 @@ public FormBrowser(UpdaterSettings updaterSettings){ contextMenu.Dispose(); notificationScreenshotManager?.Dispose(); - soundNotification?.Dispose(); videoPlayer?.Dispose(); analytics?.Dispose(); }; @@ -291,22 +288,6 @@ private void updates_UpdateDismissed(object sender, UpdateEventArgs e){ }); } - private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){ - e.Ignore = true; - - using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, MessageBoxIcon.Error)){ - form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused); - - Button btnOpenSettings = form.AddButton("View Options"); - btnOpenSettings.Width += 16; - btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y); - - if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){ - OpenSettings(typeof(TabSettingsSounds)); - } - } - } - protected override void WndProc(ref Message m){ if (isLoaded && m.Msg == Program.WindowRestoreMessage){ if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){ @@ -357,6 +338,10 @@ public void ReloadColumns(){ browser.ReloadColumns(); } + public void PlaySoundNotification(){ + browser.PlaySoundNotification(); + } + public void ApplyROT13(){ browser.ApplyROT13(); } @@ -459,19 +444,7 @@ public void OpenPlugins(){ } } - public void PlayNotificationSound(){ - if (Config.NotificationSoundPath.Length == 0){ - return; - } - - if (soundNotification == null){ - soundNotification = new SoundNotification(); - soundNotification.PlaybackError += soundNotification_PlaybackError; - } - - soundNotification.SetVolume(Config.NotificationSoundVolume); - soundNotification.Play(Config.NotificationSoundPath); - + public void OnTweetSound(){ TriggerAnalyticsEvent(AnalyticsFile.Event.SoundNotification); } diff --git a/Core/Notification/SoundNotification.cs b/Core/Notification/SoundNotification.cs index 2d6f1436..d5664010 100644 --- a/Core/Notification/SoundNotification.cs +++ b/Core/Notification/SoundNotification.cs @@ -1,32 +1,49 @@ -using System; -using TweetLib.Audio; +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using CefSharp; +using TweetDuck.Core.Controls; +using TweetDuck.Core.Other; +using TweetDuck.Core.Other.Settings; namespace TweetDuck.Core.Notification{ - sealed class SoundNotification : IDisposable{ - public string SupportedFormats => player.SupportedFormats; - public event EventHandler<PlaybackErrorEventArgs> PlaybackError; + static class SoundNotification{ + public const string SupportedFormats = "*.wav;*.ogg;*.flac;*.opus;*.weba;*.webm"; // TODO add mp3 when supported + + public static IResourceHandler CreateFileHandler(string path){ + string mimeType; - private readonly AudioPlayer player; + switch(Path.GetExtension(path)){ + case "weba": + case "webm": mimeType = "audio/webm"; break; + case "wav": mimeType = "audio/wav"; break; + case "ogg": mimeType = "audio/ogg"; break; + case "flac": mimeType = "audio/flac"; break; + case "opus": mimeType = "audio/ogg; codecs=opus"; break; + default: mimeType = null; break; + } - public SoundNotification(){ - this.player = AudioPlayer.New(); - this.player.PlaybackError += Player_PlaybackError; - } + try{ + return ResourceHandler.FromFilePath(path, mimeType); + }catch{ + FormBrowser browser = FormManager.TryFind<FormBrowser>(); - public void Play(string file){ - player.Play(file); - } + browser?.InvokeAsyncSafe(() => { + using(FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n"+path, MessageBoxIcon.Error)){ + form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused); + + Button btnViewOptions = form.AddButton("View Options"); + btnViewOptions.Width += 16; + btnViewOptions.Location = new Point(btnViewOptions.Location.X-16, btnViewOptions.Location.Y); - public bool SetVolume(int volume){ - return player.SetVolume(volume); - } + if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){ + browser.OpenSettings(typeof(TabSettingsSounds)); + } + } + }); - private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){ - PlaybackError?.Invoke(this, e); - } - - public void Dispose(){ - player.Dispose(); + return null; + } } } } diff --git a/Core/Other/FormSettings.cs b/Core/Other/FormSettings.cs index 60ae3b9a..38fd460d 100644 --- a/Core/Other/FormSettings.cs +++ b/Core/Other/FormSettings.cs @@ -40,7 +40,7 @@ public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler up AddButton("Locales", () => new TabSettingsLocales()); AddButton("System Tray", () => new TabSettingsTray()); AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins))); - AddButton("Sounds", () => new TabSettingsSounds()); + AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification)); AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins)); AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS)); diff --git a/Core/Other/Settings/TabSettingsSounds.Designer.cs b/Core/Other/Settings/TabSettingsSounds.Designer.cs index d3784de1..b5bf2596 100644 --- a/Core/Other/Settings/TabSettingsSounds.Designer.cs +++ b/Core/Other/Settings/TabSettingsSounds.Designer.cs @@ -36,6 +36,7 @@ private void InitializeComponent() { this.trackBarVolume = new System.Windows.Forms.TrackBar(); this.flowPanel = new System.Windows.Forms.FlowLayoutPanel(); this.panelVolume = new System.Windows.Forms.Panel(); + this.volumeUpdateTimer = new System.Windows.Forms.Timer(this.components); this.panelSoundNotification.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).BeginInit(); this.flowPanel.SuspendLayout(); @@ -170,6 +171,11 @@ private void InitializeComponent() { this.panelVolume.Size = new System.Drawing.Size(322, 36); this.panelVolume.TabIndex = 2; // + // volumeUpdateTimer + // + this.volumeUpdateTimer.Interval = 250; + this.volumeUpdateTimer.Tick += new System.EventHandler(this.volumeUpdateTimer_Tick); + // // TabSettingsSounds // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -201,5 +207,6 @@ private void InitializeComponent() { private System.Windows.Forms.TrackBar trackBarVolume; private System.Windows.Forms.FlowLayoutPanel flowPanel; private System.Windows.Forms.Panel panelVolume; + private System.Windows.Forms.Timer volumeUpdateTimer; } } diff --git a/Core/Other/Settings/TabSettingsSounds.cs b/Core/Other/Settings/TabSettingsSounds.cs index 8cb3c45b..759fd8ba 100644 --- a/Core/Other/Settings/TabSettingsSounds.cs +++ b/Core/Other/Settings/TabSettingsSounds.cs @@ -4,25 +4,18 @@ using System.Windows.Forms; using TweetDuck.Core.Controls; using TweetDuck.Core.Notification; -using TweetLib.Audio; namespace TweetDuck.Core.Other.Settings{ sealed partial class TabSettingsSounds : BaseTabSettings{ - private readonly SoundNotification soundNotification; - private readonly bool supportsChangingVolume; + private readonly Action playSoundNotification; - public TabSettingsSounds(){ + public TabSettingsSounds(Action playSoundNotification){ InitializeComponent(); - soundNotification = new SoundNotification(); - soundNotification.PlaybackError += sound_PlaybackError; - Disposed += (sender, args) => soundNotification.Dispose(); + this.playSoundNotification = playSoundNotification; - supportsChangingVolume = soundNotification.SetVolume(Config.NotificationSoundVolume); - toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used."); - - trackBarVolume.Enabled = supportsChangingVolume && !string.IsNullOrEmpty(Config.NotificationSoundPath); + trackBarVolume.SetValueSafe(Config.NotificationSoundVolume); labelVolumeValue.Text = trackBarVolume.Value+"%"; @@ -39,22 +32,29 @@ public override void OnReady(){ public override void OnClosing(){ Config.NotificationSoundPath = tbCustomSound.Text; + Config.NotificationSoundVolume = trackBarVolume.Value; + } + + private bool RefreshCanPlay(){ + bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text); + bool canPlay = isEmpty || File.Exists(tbCustomSound.Text); + + tbCustomSound.ForeColor = canPlay ? SystemColors.WindowText : Color.Red; + btnPlaySound.Enabled = canPlay; + btnResetSound.Enabled = !isEmpty; + return canPlay; } private void tbCustomSound_TextChanged(object sender, EventArgs e){ - bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text); - tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Red; - btnPlaySound.Enabled = !isEmpty; - btnResetSound.Enabled = !isEmpty; - trackBarVolume.Enabled = supportsChangingVolume && !isEmpty; + RefreshCanPlay(); } private void btnPlaySound_Click(object sender, EventArgs e){ - soundNotification.Play(tbCustomSound.Text); - } - - private void sound_PlaybackError(object sender, PlaybackErrorEventArgs e){ - FormMessage.Error("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, FormMessage.OK); + if (RefreshCanPlay()){ + Config.NotificationSoundPath = tbCustomSound.Text; + Config.NotificationSoundVolume = trackBarVolume.Value; + playSoundNotification(); + } } private void btnBrowseSound_Click(object sender, EventArgs e){ @@ -62,7 +62,7 @@ private void btnBrowseSound_Click(object sender, EventArgs e){ AutoUpgradeEnabled = true, DereferenceLinks = true, Title = "Custom Notification Sound", - Filter = "Sound file ("+soundNotification.SupportedFormats+")|"+soundNotification.SupportedFormats+"|All files (*.*)|*.*" + Filter = $"Sound file ({SoundNotification.SupportedFormats})|{SoundNotification.SupportedFormats}|All files (*.*)|*.*" }){ if (dialog.ShowDialog() == DialogResult.OK){ tbCustomSound.Text = dialog.FileName; @@ -75,9 +75,14 @@ private void btnResetSound_Click(object sender, EventArgs e){ } private void trackBarVolume_ValueChanged(object sender, EventArgs e){ + volumeUpdateTimer.Stop(); + volumeUpdateTimer.Start(); + labelVolumeValue.Text = trackBarVolume.Value+"%"; + } + + private void volumeUpdateTimer_Tick(object sender, EventArgs e){ Config.NotificationSoundVolume = trackBarVolume.Value; - soundNotification.SetVolume(Config.NotificationSoundVolume); - labelVolumeValue.Text = Config.NotificationSoundVolume+"%"; + volumeUpdateTimer.Stop(); } } } diff --git a/Core/TweetDeckBrowser.cs b/Core/TweetDeckBrowser.cs index 0b0623a4..47fb7ab6 100644 --- a/Core/TweetDeckBrowser.cs +++ b/Core/TweetDeckBrowser.cs @@ -7,6 +7,7 @@ using TweetDuck.Core.Controls; using TweetDuck.Core.Handling; using TweetDuck.Core.Handling.General; +using TweetDuck.Core.Notification; using TweetDuck.Core.Utils; using TweetDuck.Plugins; using TweetDuck.Plugins.Enums; @@ -36,6 +37,8 @@ public bool IsTweetDeckWebsite{ private readonly ChromiumWebBrowser browser; private readonly PluginManager plugins; + private string prevSoundNotificationPath = null; + public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge bridge){ this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){ DialogHandler = new FileDialogHandler(), @@ -70,6 +73,7 @@ public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridg Program.UserConfig.MuteToggled += UserConfig_MuteToggled; Program.UserConfig.ZoomLevelChanged += UserConfig_ZoomLevelChanged; + Program.UserConfig.SoundNotificationChanged += UserConfig_SoundNotificationInfoChanged; } // setup and management @@ -91,6 +95,7 @@ public void Dispose(){ Program.UserConfig.MuteToggled -= UserConfig_MuteToggled; Program.UserConfig.ZoomLevelChanged -= UserConfig_ZoomLevelChanged; + Program.UserConfig.SoundNotificationChanged -= UserConfig_SoundNotificationInfoChanged; browser.Dispose(); } @@ -129,6 +134,7 @@ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ ScriptLoader.ExecuteFile(e.Frame, "code.js"); InjectBrowserCSS(); ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS); + UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty); plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser); TweetDeckBridge.ResetStaticProperties(); @@ -167,6 +173,27 @@ private void UserConfig_ZoomLevelChanged(object sender, EventArgs e){ BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel); } + private void UserConfig_SoundNotificationInfoChanged(object sender, EventArgs e){ + const string soundUrl = "https://ton.twimg.com/tduck/updatesnd"; + bool hasCustomSound = Program.UserConfig.IsCustomSoundNotificationSet; + + if (prevSoundNotificationPath != Program.UserConfig.NotificationSoundPath){ + DefaultResourceHandlerFactory handlerFactory = browser.GetHandlerFactory(); + IResourceHandler resourceHandler = hasCustomSound ? SoundNotification.CreateFileHandler(Program.UserConfig.NotificationSoundPath) : null; + + if (resourceHandler != null){ + handlerFactory.RegisterHandler(soundUrl, resourceHandler); + } + else{ + handlerFactory.UnregisterHandler(soundUrl); + } + + prevSoundNotificationPath = Program.UserConfig.NotificationSoundPath; + } + + browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Program.UserConfig.NotificationSoundVolume); + } + // external handling public UpdateHandler CreateUpdateHandler(UpdaterSettings settings){ @@ -215,6 +242,10 @@ public void ReloadColumns(){ browser.ExecuteScriptAsync("TDGF_reloadColumns()"); } + public void PlaySoundNotification(){ + browser.ExecuteScriptAsync("TDGF_playSoundNotification()"); + } + public void ApplyROT13(){ browser.ExecuteScriptAsync("TDGF_applyROT13()"); } diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js index d3526bbf..1d66c8c4 100644 --- a/Resources/Scripts/code.js +++ b/Resources/Scripts/code.js @@ -507,10 +507,35 @@ // // Block: Hook into the notification sound effect. // + HTMLAudioElement.prototype.play = prependToFunction(HTMLAudioElement.prototype.play, function(){ - return $TDX.muteNotifications || $TDX.hasCustomNotificationSound; + return $TDX.muteNotifications; }); + window.TDGF_setSoundNotificationData = function(custom, volume){ + let audio = document.getElementById("update-sound"); + audio.volume = volume/100; + + const sourceId = "tduck-custom-sound-source"; + let source = document.getElementById(sourceId); + + if (custom && !source){ + source = document.createElement("source"); + source.id = sourceId; + source.src = "https://ton.twimg.com/tduck/updatesnd"; + audio.prepend(source); + } + else if (!custom && source){ + audio.removeChild(source); + } + + audio.load(); + }; + + window.TDGF_playSoundNotification = function(){ + document.getElementById("update-sound").play(); + }; + // // Block: Update highlighted column and tweet for context menu and other functionality. // diff --git a/TweetDuck.csproj b/TweetDuck.csproj index 23423925..038ed7f3 100644 --- a/TweetDuck.csproj +++ b/TweetDuck.csproj @@ -378,10 +378,6 @@ <Content Include="Resources\Scripts\update.js" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="lib\TweetLib.Audio\TweetLib.Audio.csproj"> - <Project>{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}</Project> - <Name>TweetLib.Audio</Name> - </ProjectReference> <ProjectReference Include="subprocess\TweetDuck.Browser.csproj"> <Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project> <Name>TweetDuck.Browser</Name>