Why won't the VMRPlayer sample play WMV files?

Depending which version of the SDK you have, if you try to play a WMV file using the VMRPlayer sample, you'll get a message box that says

Windows Media files (ASF,WMA,WMV) are not supported by this application

And some stuff about a "key provider" and required libraries, etc.

Background: WMV playback in DirectShow is built on top of the Windows Media Format SDK. When the Format SDK was first released,  applications needed to "unlock" the runtime and link to a special "stub library" in order to use the SDK. The VMRPlayer sample took the easier route and simply didn't support WMV, hence the message box.

Later, the SDK was changed and applications don't need the stub library to play clear content. (You still need a special lib for DRM playback.) But the VMRPlayer sample didn't get updated. Unfortunately, it turns out the sample also had a bug, because it assumed that a source filter would always have exactly one output pin. But surprise, the ASF source filter creates multiple output pins, one per stream in the file.

The latest version of the Windows SDK (for Windows Server 2008) fixes this bug. Here is the fix...

In the function CMovie::OpenMovie (in vcdplyer.cpp), replace the call to RenderFileToVMR9 with the following:

 //////////////////////////////////////////////////////////////////////////
// 
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////
#define SAFE_RELEASE(x) if (x) { x->Release(); x = NULL; }
 
#define CHECK_HR(hr) if (FAILED(hr)) { goto done; }

// RenderFileToVideoRenderer:
// Renders a media file to an existing video renderer in the graph. 
// NOTE: The caller must add the video renderer to the graph before 
// calling this method.

HRESULT RenderFileToVideoRenderer(
    IGraphBuilder *pGraph, WCHAR *wFileName, BOOL bRenderAudio = TRUE)
{
    if (pGraph == NULL || wFileName == NULL)
    {
        return E_POINTER;
    }
 
    HRESULT hr = S_OK;
 
    BOOL bRenderedAnyPin = FALSE;
 
    IBaseFilter *pSource = NULL;
    IPin *pPin = NULL;
    IFilterGraph2 *pGraph2 = NULL;
    IEnumPins *pEnum = NULL;
    IBaseFilter *pAudioRenderer = NULL;
 
    // Get the IFilterGraph2 interface from the Filter Graph Manager.
    CHECK_HR(hr = pGraph->QueryInterface(
        IID_IFilterGraph2, (void**)&pGraph2));
 
    // Add the source filter.
    CHECK_HR(hr = pGraph->AddSourceFilter(wFileName, L"SOURCE", &pSource));
 
    // Add the DSound Renderer to the graph.
    if (bRenderAudio)
    {
        CHECK_HR(hr = AddFilterByCLSID(
            pGraph, CLSID_DSoundRender, &pAudioRenderer, L"Audio Renderer"));
    }
 
    // Enumerate the pins on the source filter.
    CHECK_HR(hr = pSource->EnumPins(&pEnum));
 
    // Loop through all the pins
 
    while (S_OK == pEnum->Next(1, &pPin, NULL))
    {   
        // Try to render this pin. 
        // It's OK if we fail some pins, if at least one pin renders.
        HRESULT hr2 = pGraph2->RenderEx(
            pPin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL);
 
        pPin->Release();
 
        if (SUCCEEDED(hr2))
        {
            bRenderedAnyPin = TRUE;
        }
    }
 
done:
    SAFE_RELEASE(pEnum);
    SAFE_RELEASE(pAudioRenderer);
    SAFE_RELEASE(pGraph2);
 
    // If we succeeded to this point, make sure we rendered at least 
    // one stream.
    if (SUCCEEDED(hr))
    {
        if (!bRenderedAnyPin)
        {
            hr = VFW_E_CANNOT_RENDER;
        }
    }
 
    return hr;
}
 
///////////////////////////////////////////////////////////////////////
// Name: AddFilterByCLSID
// Desc: Create a filter by CLSID and add it to the graph.
///////////////////////////////////////////////////////////////////////
 
HRESULT AddFilterByCLSID(
    IGraphBuilder *pGraph,          // Pointer to the Filter Graph Manager.
    const GUID& clsid,              // CLSID of the filter to create.
    IBaseFilter **ppF,              // Receives a pointer to the filter.
    LPCWSTR wszName = NULL          // A name for the filter (can be NULL).
    )
{
    if (!pGraph || ! ppF) 
    {
        return E_POINTER;
    }
 
    *ppF = 0;
 
    IBaseFilter *pFilter = NULL;
    HRESULT hr = S_OK;
    
    CHECK_HR(hr = CoCreateInstance(
        clsid, 
        NULL, 
        CLSCTX_INPROC_SERVER, 
        IID_IBaseFilter, 
        (void**)&pFilter));
 
    CHECK_HR(hr = pGraph->AddFilter(pFilter, wszName));
 
    *ppF = pFilter;
    (*ppF)->AddRef();
 
done:
    SAFE_RELEASE(pFilter);
    return hr;
}