What i want to do is set the Windows Media Player's volume level. By default the volume is in-/decreased by 10% when e.g. clicking the down or up menu item (Play -> Volume -> Up), but this is, in my opinion, not fine enough (especially when e.g. skypeing with someone while listening to music).
The media player should stay an independent application.
Currently i'm using a little tool that sends app commands via SendMessage to the player with parameters as seen in spy++.
I thought of three ways to achieve my goal:
- using WASAPI to get the media player's audio session and dynamically set the volume level
- sending mouse down/up events to the volume slider of the media player host control by point
- getting an instance of the media player control via IWMPPlayer4
- including a media player control in my WPF application within a windows forms host (not preferred due to loss of independence)
Point 2 seems rather ugly due to the fact that the media player control is a COM element and has as far spy++ displays only one handle, meaning i would have to determine the exact position of the volume slider and send very precise mouse events. Additional i don't know whether this would work at all.
Point 3 has the presupposition that one can get an instance of a COM element by a handle. Since i have yet not worked with COM elements i don't know if this is possible.
Update: One can get an instance of a remote mediay player using the IWMPPlayer4
interface. Though i have to see whether one can change settings.
Point 1 has the impression on me that it would be possible without much effort.
Though i'd be facing the next problem: identifying the media players audio session. Enumerating them using IAudioSessionManager2
and displaying the name using
IAudioSessionControl2 ctrl2 = NULL;
// ...
hr = ctrl2->GetDisplayName(&name);
if (FAILED(hr))
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
String ^sessionName = gcnew String(name);
Console::WriteLine("Session name: '" + sessionName + "'");
prints most the times an emtpy string except for Mozilla Firefox and System Sounds (the other processes might not have set a session name themselfes => a default name is chosen and GetDisplayName
returns an empty string).
Update 2:
As Simon Mourier pointed out one can compare the process ids to get the right ISimpleAudioVolume
instance and it works as far as it comes to WMP to adopt the changes. The said instance is aquired the following way:
IMMDeviceEnumerator *pEnumerator = NULL;
ISimpleAudioVolume *pVolume = NULL;
IMMDevice *pDevice = NULL;
IAudioSessionManager2 *pManager = NULL;
IAudioSessionEnumerator *pSessionEnumerator = NULL;
int sessionCount = 0;
CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &pDevice);
pDevice->GetState(&deviceState);
pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**)&pManager);
pManager->GetSessionEnumerator(&pSessionEnumerator);
pSessionEnumerator->GetCount(&sessionCount);
for (int i = 0; i < sessionCount; i++)
{
IAudioSessionControl *ctrl = NULL;
IAudioSessionControl2 *ctrl2 = NULL;
DWORD processId = 0;
hr = pSessionEnumerator->GetSession(i, &ctrl);
if (FAILED(hr))
{
continue;
}
hr = ctrl->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&ctrl2);
if (FAILED(hr))
{
SafeRelease(ctrl);
continue;
}
hr = ctrl2->GetProcessId(&processId);
if (FAILED(hr))
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
if (processId == wmpProcessId)
{
hr = ctrl2->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&pVolume);
SafeRelease(ctrl);
SafeRelease(ctrl2);
break;
}
SafeRelease(ctrl);
SafeRelease(ctrl2);
}
When aquiering an ISimpleAudioVolume
instance via a IAudioClient
one has to provide a session id to have volume changes reported to event subscribers. Is this possible using this approach?
Though i know that adding a media player control to my application would be the easiest way, i'd like to not use this option if possible.