Skip to content

Commit 159b603

Browse files
aleabJohnnyCrazy
authored andcommitted
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.
1 parent 4b40a1b commit 159b603

3 files changed

Lines changed: 167 additions & 12 deletions

File tree

SpotifyAPI.Example/LocalControl.Designer.cs

Lines changed: 57 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SpotifyAPI.Example/LocalControl.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public void UpdateInfos()
7272

7373
if (status.Track != null) //Update track infos
7474
UpdateTrack(status.Track);
75+
76+
RefreshVolumeMixerVolume();
7577
}
7678

7779
public async void UpdateTrack(Track track)
@@ -106,6 +108,11 @@ public void UpdatePlayingStatus(bool playing)
106108
isPlayingLabel.Text = playing.ToString();
107109
}
108110

111+
public void RefreshVolumeMixerVolume()
112+
{
113+
volumeMixerLabel.Text = _spotify.GetSpotifyVolume().ToString(CultureInfo.InvariantCulture);
114+
}
115+
109116
private void _spotify_OnVolumeChange(object sender, VolumeChangeEventArgs e)
110117
{
111118
if (InvokeRequired)
@@ -178,6 +185,24 @@ private void skipBtn_Click(object sender, EventArgs e)
178185
_spotify.Skip();
179186
}
180187

188+
private void volumeUpBtn_Click(object sender, EventArgs e)
189+
{
190+
float currentVolume = _spotify.GetSpotifyVolume();
191+
float newVolume = currentVolume + 2.0f;
192+
_spotify.SetSpotifyVolume(newVolume >= 100.0f ? 100.0f : newVolume);
193+
194+
RefreshVolumeMixerVolume();
195+
}
196+
197+
private void volumeDownBtn_Click(object sender, EventArgs e)
198+
{
199+
float currentVolume = _spotify.GetSpotifyVolume();
200+
float newVolume = currentVolume - 2.0f;
201+
_spotify.SetSpotifyVolume(newVolume <= 0.0f ? 0.0f : newVolume);
202+
203+
RefreshVolumeMixerVolume();
204+
}
205+
181206
private static String FormatTime(double sec)
182207
{
183208
TimeSpan span = TimeSpan.FromSeconds(sec);

SpotifyAPI/Local/VolumeMixerControl.cs

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,13 @@ internal static void MuteSpotify(bool mute)
6767
Marshal.ReleaseComObject(volume);
6868
}
6969

70-
private static ISimpleAudioVolume GetSpotifyVolumeObject() {
71-
return (from p in Process.GetProcessesByName(SpotifyProcessName)
72-
let vol = GetVolumeObject(p.Id)
73-
where vol != null
74-
select vol).FirstOrDefault();
70+
private static ISimpleAudioVolume GetSpotifyVolumeObject()
71+
{
72+
var audioVolumeObjects = from p in Process.GetProcessesByName(SpotifyProcessName)
73+
let vol = GetVolumeObject(p.Id)
74+
where vol != null
75+
select vol;
76+
return audioVolumeObjects.FirstOrDefault();
7577
}
7678

7779
private static ISimpleAudioVolume GetVolumeObject(int pid)
@@ -81,10 +83,59 @@ private static ISimpleAudioVolume GetVolumeObject(int pid)
8183
IMmDevice speakers;
8284
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.ERender, ERole.EMultimedia, out speakers);
8385

86+
string defaultDeviceId;
87+
speakers.GetId(out defaultDeviceId);
88+
89+
ISimpleAudioVolume volumeControl = GetVolumeObject(pid, speakers);
90+
Marshal.ReleaseComObject(speakers);
91+
92+
if (volumeControl == null)
93+
{
94+
// If volumeControl is null, then the process's volume object might be on a different device.
95+
// This happens if the process doesn't use the default device.
96+
//
97+
// As far as Spotify is concerned, if using the "--enable-audio-graph" command line argument,
98+
// a new option becomes available in the Settings that makes it possible to change the playback device.
99+
100+
IMmDeviceCollection deviceCollection;
101+
deviceEnumerator.EnumAudioEndpoints(EDataFlow.ERender, EDeviceState.Active, out deviceCollection);
102+
103+
int count;
104+
deviceCollection.GetCount(out count);
105+
for (int i = 0; i < count; i++)
106+
{
107+
IMmDevice device;
108+
deviceCollection.Item(i, out device);
109+
110+
string deviceId;
111+
device.GetId(out deviceId);
112+
113+
try
114+
{
115+
if (deviceId == defaultDeviceId)
116+
continue;
117+
118+
volumeControl = GetVolumeObject(pid, device);
119+
if (volumeControl != null)
120+
break;
121+
}
122+
finally
123+
{
124+
Marshal.ReleaseComObject(device);
125+
}
126+
}
127+
}
128+
129+
Marshal.ReleaseComObject(deviceEnumerator);
130+
return volumeControl;
131+
}
132+
133+
private static ISimpleAudioVolume GetVolumeObject(int pid, IMmDevice device)
134+
{
84135
// activate the session manager. we need the enumerator
85136
Guid iidIAudioSessionManager2 = typeof(IAudioSessionManager2).GUID;
86137
object o;
87-
speakers.Activate(ref iidIAudioSessionManager2, 0, IntPtr.Zero, out o);
138+
device.Activate(ref iidIAudioSessionManager2, 0, IntPtr.Zero, out o);
88139
IAudioSessionManager2 mgr = (IAudioSessionManager2)o;
89140

90141
// enumerate sessions for on this device
@@ -105,23 +156,20 @@ private static ISimpleAudioVolume GetVolumeObject(int pid)
105156

106157
if (cpid == pid)
107158
{
108-
volumeControl = (ISimpleAudioVolume) ctl;
159+
volumeControl = (ISimpleAudioVolume)ctl;
109160
break;
110161
}
111162
Marshal.ReleaseComObject(ctl);
112163
}
113164
Marshal.ReleaseComObject(sessionEnumerator);
114165
Marshal.ReleaseComObject(mgr);
115-
Marshal.ReleaseComObject(speakers);
116-
Marshal.ReleaseComObject(deviceEnumerator);
117166
return volumeControl;
118167
}
119168

120169
[ComImport]
121170
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
122171
private class MMDeviceEnumerator
123172
{
124-
125173
}
126174

127175
private enum EDataFlow
@@ -140,10 +188,21 @@ private enum ERole
140188
ERoleEnumCount
141189
}
142190

191+
[Flags]
192+
private enum EDeviceState
193+
{
194+
Active = 0x00000001,
195+
Disabled = 0x00000002,
196+
NotPresent = 0x00000004,
197+
UnPlugged = 0x00000008,
198+
All = 0x0000000F
199+
}
200+
143201
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
144202
private interface IMmDeviceEnumerator
145203
{
146-
int NotImpl1();
204+
[PreserveSig]
205+
int EnumAudioEndpoints(EDataFlow dataFlow, EDeviceState stateMask, [Out] out IMmDeviceCollection deviceCollection);
147206

148207
[PreserveSig]
149208
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMmDevice ppDevice);
@@ -154,6 +213,21 @@ private interface IMmDevice
154213
{
155214
[PreserveSig]
156215
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
216+
217+
int OpenPropertyStore_NotImpl();
218+
219+
[PreserveSig]
220+
int GetId([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppstrId);
221+
}
222+
223+
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
224+
private interface IMmDeviceCollection
225+
{
226+
[PreserveSig]
227+
int GetCount(out int deviceCount);
228+
229+
[PreserveSig]
230+
int Item(int deviceIndex, [Out] out IMmDevice device);
157231
}
158232

159233
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

0 commit comments

Comments
 (0)