From 159b60331ec211d932a87b8c072e21eda0341d70 Mon Sep 17 00:00:00 2001 From: Alessandro Attard Barbini <11708634+aleab@users.noreply.github.com> Date: Mon, 12 Mar 2018 00:08:37 +0100 Subject: [PATCH] Fixed occasional COMException in GetSpotifyVolumeObject (#222) * Added volume controls in Example app * Fixed occasional COMException when using Get- or SetSpotifyVolume This exception only happens if Spotify is using an audio device different from the default one. Such a thing is only possible (as of v1.0.75.483.g7ff4a0dc) when using the "--enable-audio-graph" command line argument, that makes available the "Playback device" advanced option in Spotify. --- SpotifyAPI.Example/LocalControl.Designer.cs | 58 ++++++++++++- SpotifyAPI.Example/LocalControl.cs | 25 ++++++ SpotifyAPI/Local/VolumeMixerControl.cs | 96 ++++++++++++++++++--- 3 files changed, 167 insertions(+), 12 deletions(-) diff --git a/SpotifyAPI.Example/LocalControl.Designer.cs b/SpotifyAPI.Example/LocalControl.Designer.cs index 9b83f82b..b445d654 100644 --- a/SpotifyAPI.Example/LocalControl.Designer.cs +++ b/SpotifyAPI.Example/LocalControl.Designer.cs @@ -61,6 +61,10 @@ this.artistLinkLabel = new System.Windows.Forms.LinkLabel(); this.titleLinkLabel = new System.Windows.Forms.LinkLabel(); this.smallAlbumPicture = new System.Windows.Forms.PictureBox(); + this.volumeDownBtn = new System.Windows.Forms.Button(); + this.volumeUpBtn = new System.Windows.Forms.Button(); + this.label9 = new System.Windows.Forms.Label(); + this.volumeMixerLabel = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.bigAlbumPicture)).BeginInit(); this.groupBox1.SuspendLayout(); this.trackInfoBox.SuspendLayout(); @@ -69,7 +73,7 @@ // // bigAlbumPicture // - this.bigAlbumPicture.Location = new System.Drawing.Point(327, 13); + this.bigAlbumPicture.Location = new System.Drawing.Point(330, 13); this.bigAlbumPicture.Name = "bigAlbumPicture"; this.bigAlbumPicture.Size = new System.Drawing.Size(640, 640); this.bigAlbumPicture.TabIndex = 2; @@ -77,6 +81,10 @@ // // groupBox1 // + this.groupBox1.Controls.Add(this.volumeMixerLabel); + this.groupBox1.Controls.Add(this.label9); + this.groupBox1.Controls.Add(this.volumeUpBtn); + this.groupBox1.Controls.Add(this.volumeDownBtn); this.groupBox1.Controls.Add(this.repeatShuffleLabel); this.groupBox1.Controls.Add(this.label6); this.groupBox1.Controls.Add(this.versionLabel); @@ -428,6 +436,50 @@ this.smallAlbumPicture.TabIndex = 5; this.smallAlbumPicture.TabStop = false; // + // volumeDownBtn + // + this.volumeDownBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.volumeDownBtn.Location = new System.Drawing.Point(247, 134); + this.volumeDownBtn.Margin = new System.Windows.Forms.Padding(0); + this.volumeDownBtn.Name = "volumeDownBtn"; + this.volumeDownBtn.Size = new System.Drawing.Size(65, 24); + this.volumeDownBtn.TabIndex = 32; + this.volumeDownBtn.Text = "Volume-"; + this.volumeDownBtn.UseVisualStyleBackColor = true; + this.volumeDownBtn.Click += new System.EventHandler(this.volumeDownBtn_Click); + // + // volumeUpBtn + // + this.volumeUpBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.volumeUpBtn.Location = new System.Drawing.Point(247, 110); + this.volumeUpBtn.Margin = new System.Windows.Forms.Padding(0); + this.volumeUpBtn.Name = "volumeUpBtn"; + this.volumeUpBtn.Size = new System.Drawing.Size(65, 24); + this.volumeUpBtn.TabIndex = 33; + this.volumeUpBtn.Text = "Volume+"; + this.volumeUpBtn.UseVisualStyleBackColor = true; + this.volumeUpBtn.Click += new System.EventHandler(this.volumeUpBtn_Click); + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label9.Location = new System.Drawing.Point(7, 117); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(155, 17); + this.label9.TabIndex = 34; + this.label9.Text = "Volume Mixer\'s volume:"; + // + // volumeMixerLabel + // + this.volumeMixerLabel.AutoSize = true; + this.volumeMixerLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.volumeMixerLabel.Location = new System.Drawing.Point(168, 117); + this.volumeMixerLabel.Name = "volumeMixerLabel"; + this.volumeMixerLabel.Size = new System.Drawing.Size(13, 17); + this.volumeMixerLabel.TabIndex = 35; + this.volumeMixerLabel.Text = "-"; + // // LocalControl // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -483,5 +535,9 @@ private System.Windows.Forms.Label label6; private System.Windows.Forms.Label repeatShuffleLabel; private System.Windows.Forms.Label advertLabel; + private System.Windows.Forms.Button volumeUpBtn; + private System.Windows.Forms.Button volumeDownBtn; + private System.Windows.Forms.Label volumeMixerLabel; + private System.Windows.Forms.Label label9; } } diff --git a/SpotifyAPI.Example/LocalControl.cs b/SpotifyAPI.Example/LocalControl.cs index 83ac80c3..32775ad3 100644 --- a/SpotifyAPI.Example/LocalControl.cs +++ b/SpotifyAPI.Example/LocalControl.cs @@ -72,6 +72,8 @@ namespace SpotifyAPI.Example if (status.Track != null) //Update track infos UpdateTrack(status.Track); + + RefreshVolumeMixerVolume(); } public async void UpdateTrack(Track track) @@ -106,6 +108,11 @@ namespace SpotifyAPI.Example isPlayingLabel.Text = playing.ToString(); } + public void RefreshVolumeMixerVolume() + { + volumeMixerLabel.Text = _spotify.GetSpotifyVolume().ToString(CultureInfo.InvariantCulture); + } + private void _spotify_OnVolumeChange(object sender, VolumeChangeEventArgs e) { if (InvokeRequired) @@ -178,6 +185,24 @@ namespace SpotifyAPI.Example _spotify.Skip(); } + private void volumeUpBtn_Click(object sender, EventArgs e) + { + float currentVolume = _spotify.GetSpotifyVolume(); + float newVolume = currentVolume + 2.0f; + _spotify.SetSpotifyVolume(newVolume >= 100.0f ? 100.0f : newVolume); + + RefreshVolumeMixerVolume(); + } + + private void volumeDownBtn_Click(object sender, EventArgs e) + { + float currentVolume = _spotify.GetSpotifyVolume(); + float newVolume = currentVolume - 2.0f; + _spotify.SetSpotifyVolume(newVolume <= 0.0f ? 0.0f : newVolume); + + RefreshVolumeMixerVolume(); + } + private static String FormatTime(double sec) { TimeSpan span = TimeSpan.FromSeconds(sec); diff --git a/SpotifyAPI/Local/VolumeMixerControl.cs b/SpotifyAPI/Local/VolumeMixerControl.cs index fb6160ce..2cc3f80b 100644 --- a/SpotifyAPI/Local/VolumeMixerControl.cs +++ b/SpotifyAPI/Local/VolumeMixerControl.cs @@ -67,11 +67,13 @@ namespace SpotifyAPI.Local Marshal.ReleaseComObject(volume); } - private static ISimpleAudioVolume GetSpotifyVolumeObject() { - return (from p in Process.GetProcessesByName(SpotifyProcessName) - let vol = GetVolumeObject(p.Id) - where vol != null - select vol).FirstOrDefault(); + private static ISimpleAudioVolume GetSpotifyVolumeObject() + { + var audioVolumeObjects = from p in Process.GetProcessesByName(SpotifyProcessName) + let vol = GetVolumeObject(p.Id) + where vol != null + select vol; + return audioVolumeObjects.FirstOrDefault(); } private static ISimpleAudioVolume GetVolumeObject(int pid) @@ -81,10 +83,59 @@ namespace SpotifyAPI.Local IMmDevice speakers; deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.ERender, ERole.EMultimedia, out speakers); + string defaultDeviceId; + speakers.GetId(out defaultDeviceId); + + ISimpleAudioVolume volumeControl = GetVolumeObject(pid, speakers); + Marshal.ReleaseComObject(speakers); + + if (volumeControl == null) + { + // If volumeControl is null, then the process's volume object might be on a different device. + // This happens if the process doesn't use the default device. + // + // As far as Spotify is concerned, if using the "--enable-audio-graph" command line argument, + // a new option becomes available in the Settings that makes it possible to change the playback device. + + IMmDeviceCollection deviceCollection; + deviceEnumerator.EnumAudioEndpoints(EDataFlow.ERender, EDeviceState.Active, out deviceCollection); + + int count; + deviceCollection.GetCount(out count); + for (int i = 0; i < count; i++) + { + IMmDevice device; + deviceCollection.Item(i, out device); + + string deviceId; + device.GetId(out deviceId); + + try + { + if (deviceId == defaultDeviceId) + continue; + + volumeControl = GetVolumeObject(pid, device); + if (volumeControl != null) + break; + } + finally + { + Marshal.ReleaseComObject(device); + } + } + } + + Marshal.ReleaseComObject(deviceEnumerator); + return volumeControl; + } + + private static ISimpleAudioVolume GetVolumeObject(int pid, IMmDevice device) + { // activate the session manager. we need the enumerator Guid iidIAudioSessionManager2 = typeof(IAudioSessionManager2).GUID; object o; - speakers.Activate(ref iidIAudioSessionManager2, 0, IntPtr.Zero, out o); + device.Activate(ref iidIAudioSessionManager2, 0, IntPtr.Zero, out o); IAudioSessionManager2 mgr = (IAudioSessionManager2)o; // enumerate sessions for on this device @@ -105,15 +156,13 @@ namespace SpotifyAPI.Local if (cpid == pid) { - volumeControl = (ISimpleAudioVolume) ctl; + volumeControl = (ISimpleAudioVolume)ctl; break; } Marshal.ReleaseComObject(ctl); } Marshal.ReleaseComObject(sessionEnumerator); Marshal.ReleaseComObject(mgr); - Marshal.ReleaseComObject(speakers); - Marshal.ReleaseComObject(deviceEnumerator); return volumeControl; } @@ -121,7 +170,6 @@ namespace SpotifyAPI.Local [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] private class MMDeviceEnumerator { - } private enum EDataFlow @@ -140,10 +188,21 @@ namespace SpotifyAPI.Local ERoleEnumCount } + [Flags] + private enum EDeviceState + { + Active = 0x00000001, + Disabled = 0x00000002, + NotPresent = 0x00000004, + UnPlugged = 0x00000008, + All = 0x0000000F + } + [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IMmDeviceEnumerator { - int NotImpl1(); + [PreserveSig] + int EnumAudioEndpoints(EDataFlow dataFlow, EDeviceState stateMask, [Out] out IMmDeviceCollection deviceCollection); [PreserveSig] int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMmDevice ppDevice); @@ -154,6 +213,21 @@ namespace SpotifyAPI.Local { [PreserveSig] int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface); + + int OpenPropertyStore_NotImpl(); + + [PreserveSig] + int GetId([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppstrId); + } + + [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IMmDeviceCollection + { + [PreserveSig] + int GetCount(out int deviceCount); + + [PreserveSig] + int Item(int deviceIndex, [Out] out IMmDevice device); } [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]