Step 5: Handle Media Session Events

This topic is step 5 of the tutorial How to Play Media Files with Media Foundation. The complete code is shown in the topic Media Session Playback Example.

For background on this topic, read Media Event Generators. This topic contains the following sections:

Getting Session Events

To get events from the Media Session, the CPlayer object calls the IMFMediaEventGenerator::BeginGetEvent method, as shown in Step 4: Create the Media Session. This method is asynchronous, meaning it returns to the caller immediately. When the next session event occurs, the Media Session calls the IMFAsyncCallback::Invoke method of the CPlayer object.

It is important to remember that Invoke gets called from a worker thread, not from the application thread. Therefore, the implementation of Invoke must be multithread-safe. One approach would be to protect the member data with a critical section. However, the CPlayer class shows an alternative approach:

  1. In the Invoke method, the CPlayer object posts a WM_APP_PLAYER_EVENT message to the application. The message parameter is an IMFMediaEvent pointer.
  2. The application receives the WM_APP_PLAYER_EVENT message.
  3. The application calls the CPlayer::HandleEvent method, passing in the IMFMediaEvent pointer.
  4. The HandleEvent method responds to the event.

The following code shows the Invoke method:

//  Callback for the asynchronous BeginGetEvent method.

HRESULT CPlayer::Invoke(IMFAsyncResult *pResult)
{
    MediaEventType meType = MEUnknown;  // Event type

    IMFMediaEvent *pEvent = NULL;

    // Get the event from the event queue.
    HRESULT hr = m_pSession->EndGetEvent(pResult, &pEvent);
    if (FAILED(hr))
    {
        goto done;
    }

    // Get the event type. 
    hr = pEvent->GetType(&meType);
    if (FAILED(hr))
    {
        goto done;
    }

    if (meType == MESessionClosed)
    {
        // The session was closed. 
        // The application is waiting on the m_hCloseEvent event handle. 
        SetEvent(m_hCloseEvent);
    }
    else
    {
        // For all other events, get the next event in the queue.
        hr = m_pSession->BeginGetEvent(this, NULL);
        if (FAILED(hr))
        {
            goto done;
        }
    }

    // Check the application state. 
        
    // If a call to IMFMediaSession::Close is pending, it means the 
    // application is waiting on the m_hCloseEvent event and
    // the application's message loop is blocked. 

    // Otherwise, post a private window message to the application. 

    if (m_state != Closing)
    {
        // Leave a reference count on the event.
        pEvent->AddRef();

        PostMessage(m_hwndEvent, WM_APP_PLAYER_EVENT, 
            (WPARAM)pEvent, (LPARAM)meType);
    }

done:
    SafeRelease(&pEvent);
    return S_OK;
}

The Invoke method performs the following steps:

  1. Call IMFMediaEventGenerator::EndGetEvent to get the event. This method returns a pointer to the IMFMediaEvent interface.
  2. Call IMFMediaEvent::GetType to get the event code.
  3. If the event code is MESessionClosed, call SetEvent to set the m_hCloseEvent event. The reason for this step is explained in Step 7: Shut Down the Media Session, and also in the code comments.
  4. For all other event codes, call IMFMediaEventGenerator::BeginGetEvent to request the next event.
  5. Post a WM_APP_PLAYER_EVENT message to the window.

The following code shows the HandleEvent method, which is called when the application receives the WM_APP_PLAYER_EVENT message:

HRESULT CPlayer::HandleEvent(UINT_PTR pEventPtr)
{
    HRESULT hrStatus = S_OK;            
    MediaEventType meType = MEUnknown;  

    IMFMediaEvent *pEvent = (IMFMediaEvent*)pEventPtr;

    if (pEvent == NULL)
    {
        return E_POINTER;
    }

    // Get the event type.
    HRESULT hr = pEvent->GetType(&meType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Get the event status. If the operation that triggered the event 
    // did not succeed, the status is a failure code.
    hr = pEvent->GetStatus(&hrStatus);

    // Check if the async operation succeeded.
    if (SUCCEEDED(hr) && FAILED(hrStatus)) 
    {
        hr = hrStatus;
    }
    if (FAILED(hr))
    {
        goto done;
    }

    switch(meType)
    {
    case MESessionTopologyStatus:
        hr = OnTopologyStatus(pEvent);
        break;

    case MEEndOfPresentation:
        hr = OnPresentationEnded(pEvent);
        break;

    case MENewPresentation:
        hr = OnNewPresentation(pEvent);
        break;

    default:
        hr = OnSessionEvent(pEvent, meType);
        break;
    }

done:
    SafeRelease(&pEvent);
    return hr;
}

This method calls IMFMediaEvent::GetType to get the event type and IMFMediaEvent::GetStatus to get the success of failure code associated with the event. The next action taken depends on the event code.

MESessionTopologyStatus

The MESessionTopologyStatus event signals a change in the status of the topology. The MF_EVENT_TOPOLOGY_STATUS attribute of the event object contains the status. For this example, the only value of interest is MF_TOPOSTATUS_READY, which indicates that playback is ready to start.

HRESULT CPlayer::OnTopologyStatus(IMFMediaEvent *pEvent)
{
    UINT32 status; 

    HRESULT hr = pEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status);
    if (SUCCEEDED(hr) && (status == MF_TOPOSTATUS_READY))
    {
        SafeRelease(&m_pVideoDisplay);

        // Get the IMFVideoDisplayControl interface from EVR. This call is
        // expected to fail if the media file does not have a video stream.

        (void)MFGetService(m_pSession, MR_VIDEO_RENDER_SERVICE, 
            IID_PPV_ARGS(&m_pVideoDisplay));

        hr = StartPlayback();
    }
    return hr;
}

The CPlayer::StartPlayback method is shown in Step 6: Control Playback.

This example also calls MFGetService to get the IMFVideoDisplayControl interface from the Enhanced Video Renderer (EVR). This interface is needed to handle repainting and resizing the video window, also shown in Step 6: Control Playback.

MEEndOfPresentation

The MEEndOfPresentation event signals that playback has reached the end of the file. The Media Session automatically switches back to the stopped state.

//  Handler for MEEndOfPresentation event.
HRESULT CPlayer::OnPresentationEnded(IMFMediaEvent *pEvent)
{
    // The session puts itself into the stopped state automatically.
    m_state = Stopped;
    return S_OK;
}

MENewPresentation

The MENewPresentation event signals the start of a new presentation. The event data is an IMFPresentationDescriptor pointer for the new presentation.

In many cases, you will not receive this event at all. If you do, use the IMFPresentationDescriptor pointer to create a new playback topology, as shown in Step 3: Open a Media File. Then queue the new topology on the Media Session.

//  Handler for MENewPresentation event.
//
//  This event is sent if the media source has a new presentation, which 
//  requires a new topology. 

HRESULT CPlayer::OnNewPresentation(IMFMediaEvent *pEvent)
{
    IMFPresentationDescriptor *pPD = NULL;
    IMFTopology *pTopology = NULL;

    // Get the presentation descriptor from the event.
    HRESULT hr = GetEventObject(pEvent, &pPD);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create a partial topology.
    hr = CreatePlaybackTopology(m_pSource, pPD,  m_hwndVideo,&pTopology);
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the topology on the media session.
    hr = m_pSession->SetTopology(0, pTopology);
    if (FAILED(hr))
    {
        goto done;
    }

    m_state = OpenPending;

done:
    SafeRelease(&pTopology);
    SafeRelease(&pPD);
    return S_OK;
}

Next: Step 6: Control Playback

Audio/Video Playback

How to Play Media Files with Media Foundation