diff --git a/SpotifyAPI/Local/SpotifyLocalAPI.cs b/SpotifyAPI/Local/SpotifyLocalAPI.cs index 5c599189..383b582f 100644 --- a/SpotifyAPI/Local/SpotifyLocalAPI.cs +++ b/SpotifyAPI/Local/SpotifyLocalAPI.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.IO; using System.Runtime.InteropServices; using System.Timers; @@ -13,9 +14,6 @@ namespace SpotifyAPI.Local [DllImport("user32.dll")] private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo); - [DllImport("nircmd.dll", CharSet = CharSet.Auto)] - private static extern bool DoNirCmd(String nirCmdStr); - private bool _listenForEvents; public bool ListenForEvents @@ -159,21 +157,72 @@ namespace SpotifyAPI.Local } /// - /// Mutes Spotify (Requires nircmd.dll) + /// Mutes Spotify (Requires Windows 7 or newer) /// public void Mute() { - if (File.Exists("nircmd.dll")) - DoNirCmd("muteappvolume spotify.exe 1"); + //Windows < Windows Vista Check + Contract.Requires(Environment.OSVersion.Version.Major >= 6); + //Windows Vista Check + if (Environment.OSVersion.Version.Major == 6) + Contract.Requires(Environment.OSVersion.Version.Minor != 0); + VolumeMixerControl.MuteSpotify(true); } /// - /// Unmutes Spotify (Requires nircmd.dll) + /// Unmutes Spotify (Requires Windows 7 or newer) /// public void UnMute() { - if (File.Exists("nircmd.dll")) - DoNirCmd("muteappvolume spotify.exe 0"); + //Windows < Windows Vista Check + Contract.Requires(Environment.OSVersion.Version.Major >= 6); + //Windows Vista Check + if (Environment.OSVersion.Version.Major == 6) + Contract.Requires(Environment.OSVersion.Version.Minor != 0); + VolumeMixerControl.MuteSpotify(false); + } + + /// + /// Checks whether Spotify is muted in the Volume Mixer control + /// + /// Null if an error occured, otherwise the muted state + public bool? IsSpotifyMuted() + { + //Windows < Windows Vista Check + Contract.Requires(Environment.OSVersion.Version.Major >= 6); + //Windows Vista Check + if (Environment.OSVersion.Version.Major == 6) + Contract.Requires(Environment.OSVersion.Version.Minor != 0); + return VolumeMixerControl.IsSpotifyMuted(); + } + + /// + /// Sets the Volume Mixer volume (requires Windows 7 or newer) + /// + /// A Value between 0 and 100 + public void SetSpotifyVolume(float volume = 100) + { + Contract.Requires(0 <= volume && volume <= 100); + //Windows < Windows Vista Check + Contract.Requires(Environment.OSVersion.Version.Major >= 6); + //Windows Vista Check + if (Environment.OSVersion.Version.Major == 6) + Contract.Requires(Environment.OSVersion.Version.Minor != 0); + VolumeMixerControl.SetSpotifyVolume(volume); + } + + /// + /// Return the Volume Mixer volume of Spotify (requires Windows 7 or newer) + /// + /// Null if an error occured, otherwise a float between 0 and 100 + public float? GetSpotifyVolume() + { + //Windows < Windows Vista Check + Contract.Requires(Environment.OSVersion.Version.Major >= 6); + //Windows Vista Check + if (Environment.OSVersion.Version.Major == 6) + Contract.Requires(Environment.OSVersion.Version.Minor != 0); + return VolumeMixerControl.GetSpotifyVolume(); } /// diff --git a/SpotifyAPI/Local/VolumeMixerControl.cs b/SpotifyAPI/Local/VolumeMixerControl.cs new file mode 100644 index 00000000..29247818 --- /dev/null +++ b/SpotifyAPI/Local/VolumeMixerControl.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace SpotifyAPI.Local +{ + internal class VolumeMixerControl + { + private const String SPOTIFY_PROCESS_NAME = "spotify"; + + internal static float? GetSpotifyVolume() + { + Process[] p = Process.GetProcessesByName(SPOTIFY_PROCESS_NAME); + if (p.Length == 0) + return null; + + int pid = p[0].Id; + + ISimpleAudioVolume volume = GetVolumeObject(pid); + if (volume == null) + return null; + + float level; + volume.GetMasterVolume(out level); + Marshal.ReleaseComObject(volume); + return level * 100; + } + + internal static bool? IsSpotifyMuted() + { + Process[] p = Process.GetProcessesByName(SPOTIFY_PROCESS_NAME); + if (p.Length == 0) + return null; + + int pid = p[0].Id; + + ISimpleAudioVolume volume = GetVolumeObject(pid); + if (volume == null) + return null; + + bool mute; + volume.GetMute(out mute); + Marshal.ReleaseComObject(volume); + return mute; + } + + internal static void SetSpotifyVolume(float level) + { + Process[] p = Process.GetProcessesByName(SPOTIFY_PROCESS_NAME); + if (p.Length == 0) + return; + + int pid = p[0].Id; + + ISimpleAudioVolume volume = GetVolumeObject(pid); + if (volume == null) + return; + + Guid guid = Guid.Empty; + volume.SetMasterVolume(level / 100, ref guid); + Marshal.ReleaseComObject(volume); + } + + internal static void MuteSpotify(bool mute) + { + Process[] p = Process.GetProcessesByName(SPOTIFY_PROCESS_NAME); + if (p.Length == 0) + return; + + int pid = p[0].Id; + + ISimpleAudioVolume volume = GetVolumeObject(pid); + if (volume == null) + return; + + Guid guid = Guid.Empty; + volume.SetMute(mute, ref guid); + Marshal.ReleaseComObject(volume); + } + + private static ISimpleAudioVolume GetVolumeObject(int pid) + { + // get the speakers (1st render + multimedia) device + IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); + IMMDevice speakers; + deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers); + + // activate the session manager. we need the enumerator + Guid IID_IAudioSessionManager2 = typeof(IAudioSessionManager2).GUID; + object o; + speakers.Activate(ref IID_IAudioSessionManager2, 0, IntPtr.Zero, out o); + IAudioSessionManager2 mgr = (IAudioSessionManager2)o; + + // enumerate sessions for on this device + IAudioSessionEnumerator sessionEnumerator; + mgr.GetSessionEnumerator(out sessionEnumerator); + int count; + sessionEnumerator.GetCount(out count); + + // search for an audio session with the required name + // NOTE: we could also use the process id instead of the app name (with IAudioSessionControl2) + ISimpleAudioVolume volumeControl = null; + for (int i = 0; i < count; i++) + { + IAudioSessionControl2 ctl; + sessionEnumerator.GetSession(i, out ctl); + int cpid; + ctl.GetProcessId(out cpid); + + if (cpid == pid) + { + volumeControl = ctl as ISimpleAudioVolume; + break; + } + Marshal.ReleaseComObject(ctl); + } + Marshal.ReleaseComObject(sessionEnumerator); + Marshal.ReleaseComObject(mgr); + Marshal.ReleaseComObject(speakers); + Marshal.ReleaseComObject(deviceEnumerator); + return volumeControl; + } + + + [ComImport] + [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] + private class MMDeviceEnumerator + { + } + + private enum EDataFlow + { + eRender, + eCapture, + eAll, + EDataFlow_enum_count + } + + private enum ERole + { + eConsole, + eMultimedia, + eCommunications, + ERole_enum_count + } + + [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IMMDeviceEnumerator + { + int NotImpl1(); + + [PreserveSig] + int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice); + } + + [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IMMDevice + { + [PreserveSig] + int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface); + + } + + [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IAudioSessionManager2 + { + int NotImpl1(); + int NotImpl2(); + + [PreserveSig] + int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum); + } + + [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IAudioSessionEnumerator + { + [PreserveSig] + int GetCount(out int SessionCount); + + [PreserveSig] + int GetSession(int SessionCount, out IAudioSessionControl2 Session); + } + + [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface ISimpleAudioVolume + { + [PreserveSig] + int SetMasterVolume(float fLevel, ref Guid EventContext); + + [PreserveSig] + int GetMasterVolume(out float pfLevel); + + [PreserveSig] + int SetMute(bool bMute, ref Guid EventContext); + + [PreserveSig] + int GetMute(out bool pbMute); + } + + [Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IAudioSessionControl2 + { + [PreserveSig] + int NotImpl0(); + + [PreserveSig] + int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); + + [PreserveSig] + int SetDisplayName([MarshalAs(UnmanagedType.LPWStr)]string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); + + [PreserveSig] + int GetIconPath([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); + + [PreserveSig] + int SetIconPath([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); + + [PreserveSig] + int GetGroupingParam(out Guid pRetVal); + + [PreserveSig] + int SetGroupingParam([MarshalAs(UnmanagedType.LPStruct)] Guid Override, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); + + [PreserveSig] + int NotImpl1(); + + [PreserveSig] + int NotImpl2(); + + [PreserveSig] + int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); + + [PreserveSig] + int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); + + [PreserveSig] + int GetProcessId(out int pRetVal); + + [PreserveSig] + int IsSystemSoundsSession(); + + [PreserveSig] + int SetDuckingPreference(bool optOut); + } + } +} diff --git a/SpotifyAPI/SpotifyAPI.csproj b/SpotifyAPI/SpotifyAPI.csproj index de89e8af..faaaafb5 100644 --- a/SpotifyAPI/SpotifyAPI.csproj +++ b/SpotifyAPI/SpotifyAPI.csproj @@ -56,6 +56,7 @@ Component +