Etapa 5: Manipular eventos de sessão de mídia

Este tópico é a etapa 5 do tutorial How to Play Media Files with Media Foundation. O código completo é mostrado no tópico Exemplo de Reprodução da Sessão de Mídia.

Para obter informações sobre este tópico, leia Geradores de Eventos de Mídia. Este tópico contém as seguintes seções:

Obtendo eventos de sessão

Para obter eventos da Sessão de Mídia, o objeto CPlayer chama o método IMFMediaEventGenerator::BeginGetEvent , conforme mostrado na Etapa 4: Criar a Sessão de Mídia. Esse método é assíncrono, o que significa que ele retorna ao chamador imediatamente. Quando o próximo evento de sessão ocorrer, a Sessão de Mídia chamará o método IMFAsyncCallback::Invoke do objeto CPlayer.

É importante lembrar que Invoke é chamado de um thread de trabalho, não do thread do aplicativo. Portanto, a implementação de Invoke deve ser multithread-safe. Uma abordagem seria proteger os dados do membro com uma seção crítica. No entanto, a CPlayer classe mostra uma abordagem alternativa:

  1. No método Invoke , o objeto CPlayer posta uma mensagem WM_APP_PLAYER_EVENT para o aplicativo. O parâmetro de mensagem é um ponteiro IMFMediaEvent .
  2. O aplicativo recebe a mensagem WM_APP_PLAYER_EVENT .
  3. O aplicativo chama o CPlayer::HandleEvent método, passando o ponteiro IMFMediaEvent .
  4. O HandleEvent método responde ao evento.

O código a seguir mostra o método Invoke :

//  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;
}

O método Invoke executa as seguintes etapas:

  1. Chame IMFMediaEventGenerator::EndGetEvent para obter o evento. Esse método retorna um ponteiro para a interface IMFMediaEvent .
  2. Chame IMFMediaEvent::GetType para obter o código do evento.
  3. Se o código de evento for MESessionClosed, chame SetEvent para definir o evento m_hCloseEvent . O motivo dessa etapa é explicado na Etapa 7: Desligar a Sessão de Mídia e também nos comentários de código.
  4. Para todos os outros códigos de evento, chame IMFMediaEventGenerator::BeginGetEvent para solicitar o próximo evento.
  5. Poste uma mensagem de WM_APP_PLAYER_EVENT na janela.

O código a seguir mostra o método HandleEvent, que é chamado quando o aplicativo recebe a mensagem WM_APP_PLAYER_EVENT :

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;
}

Esse método chama IMFMediaEvent::GetType para obter o tipo de evento e IMFMediaEvent::GetStatus para obter o sucesso do código de falha associado ao evento. A próxima ação executada depende do código do evento.

MESessionTopologyStatus

O evento MESessionTopologyStatus sinaliza uma alteração no status da topologia. O atributo MF_EVENT_TOPOLOGY_STATUS do objeto de evento contém o status. Para este exemplo, o único valor de interesse é MF_TOPOSTATUS_READY, o que indica que a reprodução está pronta para começar.

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;
}

O CPlayer::StartPlayback método é mostrado na Etapa 6: Reprodução de Controle.

Este exemplo também chama MFGetService para obter a interface IMFVideoDisplayControl do Renderizador de Vídeo Avançado (EVR). Essa interface é necessária para lidar com a repintação e redimensionamento da janela de vídeo, também mostrada na Etapa 6: Reprodução de controle.

MEEndOfPresentation

O evento MEEndOfPresentation sinaliza que a reprodução chegou ao final do arquivo. A Sessão de Mídia volta automaticamente para o estado interrompido.

//  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

O evento MENewPresentation sinaliza o início de uma nova apresentação. Os dados do evento são um ponteiro IMFPresentationDescriptor para a nova apresentação.

Em muitos casos, você não receberá esse evento. Se você fizer isso, use o ponteiro IMFPresentationDescriptor para criar uma nova topologia de reprodução, conforme mostrado na Etapa 3: Abrir um Arquivo de Mídia. Em seguida, enfileira a nova topologia na Sessão de Mídia.

//  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;
}

Próximo: Etapa 6: Reprodução de controle

Reprodução de áudio/vídeo

Como reproduzir arquivos de mídia com a Media Foundation