Streamline Streamline -4 years ago 160
C++ Question

Set WMP volume level

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.

Answer Source

I don't know what may happened during my initial try to set the media player's volume level, but the following code works (most exception handling excluded):

HRESULT                 hr;
IMMDeviceEnumerator     *pEnumerator = NULL;
ISimpleAudioVolume      *pVolume = NULL;
IMMDevice               *pDevice = NULL;
IAudioSessionManager2   *pManager = NULL;
IAudioSessionEnumerator *pSessionEnumerator = NULL;
int                      sessionCount = 0;
int                      wmpProcess = GetWmpProcessId(); // Aquire WMPs process id

// Get the device enumerator and initialize the application for COM
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
         __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);

// Get the default device
hr = pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,
         ERole::eMultimedia, &pDevice);

// Get the session 2 manager
hr = pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL,
         NULL, (void**)&pManager);

// Get the session enumerator
hr = pManager->GetSessionEnumerator(&pSessionEnumerator);

// Get the session count
hr = pSessionEnumerator->GetCount(&sessionCount);

// Loop through all sessions
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;
    }

    //Identify WMP process
    hr = ctrl2->GetProcessId(&processId);

    if (FAILED(hr))
    {
        SafeRelease(ctrl);
        SafeRelease(ctrl2);
        continue;
    }

    if (processId != wmpProcess)
    {
        SafeRelease(ctrl);
        SafeRelease(ctrl2);
        continue;
    }

    hr = ctrl2->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&pVolume);

    if (FAILED(hr))
    {
        Error(hr, "Failed to get ISimpleAudioVolume.");

        SafeRelease(ctrl);
        SafeRelease(ctrl2);
        continue;
    }

    // Set the master volume
    hr = pVolume->SetMasterVolume(1.0, NULL);

    if (FAILED(hr))
    {
        Error(hr, "Failed to set the master volume.");
        SafeRelease(ctrl);
        SafeRelease(ctrl2);
        SafeRelease(pVolume);
        continue;
    }

    SafeRelease(ctrl);
    SafeRelease(ctrl2);
    SafeRelease(pVolume);
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download