步驟 5:處理媒體會話事件

本主題是 如何使用 Media Foundation 播放媒體檔案教學課程的步驟 5。 完整的程式碼會顯示在 媒體會話播放範例主題中。

如需本主題的背景,請參閱 媒體事件產生器。 本主題包含下列幾節:

取得會話事件

若要從媒體會話取得事件,CPlayer 物件會呼叫 IMFMediaEventGenerator::BeginGetEvent 方法,如 步驟 4:建立媒體會話所示。 這個方法是非同步,這表示它會立即傳回給呼叫端。 發生下一個會話事件時,媒體會話會呼叫 CPlayer 物件的 IMFAsyncCallback::Invoke 方法。

請務必記住 ,叫 用會從背景工作執行緒呼叫,而不是從應用程式執行緒呼叫。 因此, Invoke 的實作必須是多執行緒安全。 其中一種方法是使用重要區段來保護成員資料。 不過,類別 CPlayer 會顯示替代方法:

  1. Invoke 方法中,CPlayer 物件會將 WM_APP_PLAYER_EVENT 訊息張貼至應用程式。 訊息參數是 IMFMediaEvent 指標。
  2. 應用程式會收到 WM_APP_PLAYER_EVENT 訊息。
  3. 應用程式會呼叫 CPlayer::HandleEvent 方法,傳入 IMFMediaEvent 指標。
  4. 方法 HandleEvent 會回應 事件。

下列程式碼顯示 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;
}

Invoke方法會執行下列步驟:

  1. 呼叫 IMFMediaEventGenerator::EndGetEvent 以取得事件。 這個方法會傳回 IMFMediaEvent 介面的指標。
  2. 呼叫 IMFMediaEvent::GetType 以取得事件程式碼。
  3. 如果事件程式碼是 MESessionClosed,請呼叫 SetEvent 來設定 m_hCloseEvent 事件。 此步驟的原因會在 步驟 7:關閉媒體會話以及程式碼批註中說明。
  4. 針對所有其他事件代碼,呼叫 IMFMediaEventGenerator::BeginGetEvent 以要求下一個事件。
  5. WM_APP_PLAYER_EVENT 訊息張貼至視窗。

下列程式碼顯示 HandleEvent 方法,此方法會在應用程式收到 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;
}

這個方法會呼叫 IMFMediaEvent::GetType 來取得事件種類和 IMFMediaEvent::GetStatus ,以取得與事件相關聯的失敗碼成功。 下一個採取的動作取決於事件程式碼。

MESessionTopologyStatus

MESessionTopologyStatus事件會發出拓撲狀態變更的訊號。 事件物件的 MF_EVENT_TOPOLOGY_STATUS 屬性包含狀態。 在此範例中,感興趣的唯一值 是MF_TOPOSTATUS_READY,這表示播放已準備好開始。

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

方法 CPlayer::StartPlayback 會顯示在 步驟 6:控制項播放中。

此範例也會呼叫MFGetService,從增強式視訊轉譯器 (EVR) 取得IMFVideoDisplayControl介面。 需要此介面來處理重繪和調整視訊視窗的大小,也會顯示在 步驟 6:控制項播放中。

MEEndOfPresentation

MEEndOfPresentation事件表示播放已到達檔案結尾。 媒體會話會自動切換回停止狀態。

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

MENewPresentation事件會發出新簡報的開始訊號。 事件資料是新簡報的 IMFPresentationDescriptor 指標。

在許多情況下,您完全不會收到此事件。 如果您這麼做,請使用 IMFPresentationDescriptor 指標來建立新的播放拓撲,如 步驟 3:開啟媒體檔案所示。 然後將媒體會話上的新拓撲排入佇列。

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

下一 步:步驟 6:控制項播放

音訊/視訊播放

如何使用媒體基礎播放媒體檔案