Skip to content

Commit 53ff432

Browse files
Removed nircmd.exe
Changed Mute and UnMute function to work with both x86 and x64 processes on Windows 7 or newer Added functions to manipulate the Volume Control of Spotify
1 parent 49441bb commit 53ff432

3 files changed

Lines changed: 309 additions & 9 deletions

File tree

‎SpotifyAPI/Local/SpotifyLocalAPI.cs‎

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.ComponentModel;
44
using System.Diagnostics;
5+
using System.Diagnostics.Contracts;
56
using System.IO;
67
using System.Runtime.InteropServices;
78
using System.Timers;
@@ -13,9 +14,6 @@ public class SpotifyLocalAPI
1314
[DllImport("user32.dll")]
1415
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
1516

16-
[DllImport("nircmd.dll", CharSet = CharSet.Auto)]
17-
private static extern bool DoNirCmd(String nirCmdStr);
18-
1917
private bool _listenForEvents;
2018

2119
public bool ListenForEvents
@@ -159,21 +157,72 @@ public StatusResponse GetStatus()
159157
}
160158

161159
/// <summary>
162-
/// Mutes Spotify (Requires nircmd.dll)
160+
/// Mutes Spotify (Requires Windows 7 or newer)
163161
/// </summary>
164162
public void Mute()
165163
{
166-
if (File.Exists("nircmd.dll"))
167-
DoNirCmd("muteappvolume spotify.exe 1");
164+
//Windows < Windows Vista Check
165+
Contract.Requires(Environment.OSVersion.Version.Major >= 6);
166+
//Windows Vista Check
167+
if (Environment.OSVersion.Version.Major == 6)
168+
Contract.Requires(Environment.OSVersion.Version.Minor != 0);
169+
VolumeMixerControl.MuteSpotify(true);
168170
}
169171

170172
/// <summary>
171-
/// Unmutes Spotify (Requires nircmd.dll)
173+
/// Unmutes Spotify (Requires Windows 7 or newer)
172174
/// </summary>
173175
public void UnMute()
174176
{
175-
if (File.Exists("nircmd.dll"))
176-
DoNirCmd("muteappvolume spotify.exe 0");
177+
//Windows < Windows Vista Check
178+
Contract.Requires(Environment.OSVersion.Version.Major >= 6);
179+
//Windows Vista Check
180+
if (Environment.OSVersion.Version.Major == 6)
181+
Contract.Requires(Environment.OSVersion.Version.Minor != 0);
182+
VolumeMixerControl.MuteSpotify(false);
183+
}
184+
185+
/// <summary>
186+
/// Checks whether Spotify is muted in the Volume Mixer control
187+
/// </summary>
188+
/// <returns>Null if an error occured, otherwise the muted state</returns>
189+
public bool? IsSpotifyMuted()
190+
{
191+
//Windows < Windows Vista Check
192+
Contract.Requires(Environment.OSVersion.Version.Major >= 6);
193+
//Windows Vista Check
194+
if (Environment.OSVersion.Version.Major == 6)
195+
Contract.Requires(Environment.OSVersion.Version.Minor != 0);
196+
return VolumeMixerControl.IsSpotifyMuted();
197+
}
198+
199+
/// <summary>
200+
/// Sets the Volume Mixer volume (requires Windows 7 or newer)
201+
/// </summary>
202+
/// <param name="volume">A Value between 0 and 100</param>
203+
public void SetSpotifyVolume(float volume = 100)
204+
{
205+
Contract.Requires(0 <= volume && volume <= 100);
206+
//Windows < Windows Vista Check
207+
Contract.Requires(Environment.OSVersion.Version.Major >= 6);
208+
//Windows Vista Check
209+
if (Environment.OSVersion.Version.Major == 6)
210+
Contract.Requires(Environment.OSVersion.Version.Minor != 0);
211+
VolumeMixerControl.SetSpotifyVolume(volume);
212+
}
213+
214+
/// <summary>
215+
/// Return the Volume Mixer volume of Spotify (requires Windows 7 or newer)
216+
/// </summary>
217+
/// <returns>Null if an error occured, otherwise a float between 0 and 100</returns>
218+
public float? GetSpotifyVolume()
219+
{
220+
//Windows < Windows Vista Check
221+
Contract.Requires(Environment.OSVersion.Version.Major >= 6);
222+
//Windows Vista Check
223+
if (Environment.OSVersion.Version.Major == 6)
224+
Contract.Requires(Environment.OSVersion.Version.Minor != 0);
225+
return VolumeMixerControl.GetSpotifyVolume();
177226
}
178227

179228
/// <summary>
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Runtime.InteropServices;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace SpotifyAPI.Local
10+
{
11+
internal class VolumeMixerControl
12+
{
13+
private const String SPOTIFY_PROCESS_NAME = "spotify";
14+
15+
internal static float? GetSpotifyVolume()
16+
{
17+
Process[] p = Process.GetProcessesByName(SPOTIFY_PROCESS_NAME);
18+
if (p.Length == 0)
19+
return null;
20+
21+
int pid = p[0].Id;
22+
23+
ISimpleAudioVolume volume = GetVolumeObject(pid);
24+
if (volume == null)
25+
return null;
26+
27+
float level;
28+
volume.GetMasterVolume(out level);
29+
Marshal.ReleaseComObject(volume);
30+
return level * 100;
31+
}
32+
33+
internal static bool? IsSpotifyMuted()
34+
{
35+
Process[] p = Process.GetProcessesByName(SPOTIFY_PROCESS_NAME);
36+
if (p.Length == 0)
37+
return null;
38+
39+
int pid = p[0].Id;
40+
41+
ISimpleAudioVolume volume = GetVolumeObject(pid);
42+
if (volume == null)
43+
return null;
44+
45+
bool mute;
46+
volume.GetMute(out mute);
47+
Marshal.ReleaseComObject(volume);
48+
return mute;
49+
}
50+
51+
internal static void SetSpotifyVolume(float level)
52+
{
53+
Process[] p = Process.GetProcessesByName(SPOTIFY_PROCESS_NAME);
54+
if (p.Length == 0)
55+
return;
56+
57+
int pid = p[0].Id;
58+
59+
ISimpleAudioVolume volume = GetVolumeObject(pid);
60+
if (volume == null)
61+
return;
62+
63+
Guid guid = Guid.Empty;
64+
volume.SetMasterVolume(level / 100, ref guid);
65+
Marshal.ReleaseComObject(volume);
66+
}
67+
68+
internal static void MuteSpotify(bool mute)
69+
{
70+
Process[] p = Process.GetProcessesByName(SPOTIFY_PROCESS_NAME);
71+
if (p.Length == 0)
72+
return;
73+
74+
int pid = p[0].Id;
75+
76+
ISimpleAudioVolume volume = GetVolumeObject(pid);
77+
if (volume == null)
78+
return;
79+
80+
Guid guid = Guid.Empty;
81+
volume.SetMute(mute, ref guid);
82+
Marshal.ReleaseComObject(volume);
83+
}
84+
85+
private static ISimpleAudioVolume GetVolumeObject(int pid)
86+
{
87+
// get the speakers (1st render + multimedia) device
88+
IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
89+
IMMDevice speakers;
90+
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers);
91+
92+
// activate the session manager. we need the enumerator
93+
Guid IID_IAudioSessionManager2 = typeof(IAudioSessionManager2).GUID;
94+
object o;
95+
speakers.Activate(ref IID_IAudioSessionManager2, 0, IntPtr.Zero, out o);
96+
IAudioSessionManager2 mgr = (IAudioSessionManager2)o;
97+
98+
// enumerate sessions for on this device
99+
IAudioSessionEnumerator sessionEnumerator;
100+
mgr.GetSessionEnumerator(out sessionEnumerator);
101+
int count;
102+
sessionEnumerator.GetCount(out count);
103+
104+
// search for an audio session with the required name
105+
// NOTE: we could also use the process id instead of the app name (with IAudioSessionControl2)
106+
ISimpleAudioVolume volumeControl = null;
107+
for (int i = 0; i < count; i++)
108+
{
109+
IAudioSessionControl2 ctl;
110+
sessionEnumerator.GetSession(i, out ctl);
111+
int cpid;
112+
ctl.GetProcessId(out cpid);
113+
114+
if (cpid == pid)
115+
{
116+
volumeControl = ctl as ISimpleAudioVolume;
117+
break;
118+
}
119+
Marshal.ReleaseComObject(ctl);
120+
}
121+
Marshal.ReleaseComObject(sessionEnumerator);
122+
Marshal.ReleaseComObject(mgr);
123+
Marshal.ReleaseComObject(speakers);
124+
Marshal.ReleaseComObject(deviceEnumerator);
125+
return volumeControl;
126+
}
127+
128+
129+
[ComImport]
130+
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
131+
private class MMDeviceEnumerator
132+
{
133+
}
134+
135+
private enum EDataFlow
136+
{
137+
eRender,
138+
eCapture,
139+
eAll,
140+
EDataFlow_enum_count
141+
}
142+
143+
private enum ERole
144+
{
145+
eConsole,
146+
eMultimedia,
147+
eCommunications,
148+
ERole_enum_count
149+
}
150+
151+
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
152+
private interface IMMDeviceEnumerator
153+
{
154+
int NotImpl1();
155+
156+
[PreserveSig]
157+
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
158+
}
159+
160+
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
161+
private interface IMMDevice
162+
{
163+
[PreserveSig]
164+
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
165+
166+
}
167+
168+
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
169+
private interface IAudioSessionManager2
170+
{
171+
int NotImpl1();
172+
int NotImpl2();
173+
174+
[PreserveSig]
175+
int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum);
176+
}
177+
178+
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
179+
private interface IAudioSessionEnumerator
180+
{
181+
[PreserveSig]
182+
int GetCount(out int SessionCount);
183+
184+
[PreserveSig]
185+
int GetSession(int SessionCount, out IAudioSessionControl2 Session);
186+
}
187+
188+
[Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
189+
private interface ISimpleAudioVolume
190+
{
191+
[PreserveSig]
192+
int SetMasterVolume(float fLevel, ref Guid EventContext);
193+
194+
[PreserveSig]
195+
int GetMasterVolume(out float pfLevel);
196+
197+
[PreserveSig]
198+
int SetMute(bool bMute, ref Guid EventContext);
199+
200+
[PreserveSig]
201+
int GetMute(out bool pbMute);
202+
}
203+
204+
[Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
205+
private interface IAudioSessionControl2
206+
{
207+
[PreserveSig]
208+
int NotImpl0();
209+
210+
[PreserveSig]
211+
int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
212+
213+
[PreserveSig]
214+
int SetDisplayName([MarshalAs(UnmanagedType.LPWStr)]string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
215+
216+
[PreserveSig]
217+
int GetIconPath([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
218+
219+
[PreserveSig]
220+
int SetIconPath([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
221+
222+
[PreserveSig]
223+
int GetGroupingParam(out Guid pRetVal);
224+
225+
[PreserveSig]
226+
int SetGroupingParam([MarshalAs(UnmanagedType.LPStruct)] Guid Override, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
227+
228+
[PreserveSig]
229+
int NotImpl1();
230+
231+
[PreserveSig]
232+
int NotImpl2();
233+
234+
[PreserveSig]
235+
int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
236+
237+
[PreserveSig]
238+
int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
239+
240+
[PreserveSig]
241+
int GetProcessId(out int pRetVal);
242+
243+
[PreserveSig]
244+
int IsSystemSoundsSession();
245+
246+
[PreserveSig]
247+
int SetDuckingPreference(bool optOut);
248+
}
249+
}
250+
}

‎SpotifyAPI/SpotifyAPI.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
<Compile Include="Local\ExtendedWebClient.cs">
5757
<SubType>Component</SubType>
5858
</Compile>
59+
<Compile Include="Local\VolumeMixerControl.cs" />
5960
<Compile Include="Web\IClient.cs" />
6061
<Compile Include="Local\Models\CFID.cs" />
6162
<Compile Include="Local\Enums\AlbumArtSize.cs" />

0 commit comments

Comments
 (0)