Bagikan melalui


Langkah 5: Menangani Peristiwa Sesi Media

Topik ini adalah langkah 5 dari tutorial Cara Memutar File Media dengan Media Foundation. Kode lengkap ditampilkan dalam topik Contoh Pemutaran Sesi Media.

Untuk latar belakang tentang topik ini, baca Generator Peristiwa Media. Topik ini berisi bagian berikut:

Mendapatkan Peristiwa Sesi

Untuk mendapatkan peristiwa dari Sesi Media, objek CPlayer memanggil metode IMFMediaEventGenerator::BeginGetEvent , seperti yang ditunjukkan pada Langkah 4: Buat Sesi Media. Metode ini asinkron, yang berarti segera kembali ke pemanggil. Ketika peristiwa sesi berikutnya terjadi, Sesi Media memanggil metode IMFAsyncCallback::Invoke objek CPlayer.

Penting untuk diingat bahwa Invoke dipanggil dari utas pekerja, bukan dari utas aplikasi. Oleh karena itu, implementasi Invoke harus multithread-safe. Salah satu pendekatannya adalah melindungi data anggota dengan bagian penting. Namun, CPlayer kelas menunjukkan pendekatan alternatif:

  1. Dalam metode Panggil , objek CPlayer memposting pesan WM_APP_PLAYER_EVENT ke aplikasi. Parameter pesan adalah penunjuk IMFMediaEvent .
  2. Aplikasi menerima pesan WM_APP_PLAYER_EVENT .
  3. Aplikasi memanggil CPlayer::HandleEvent metode , meneruskan penunjuk IMFMediaEvent .
  4. Metode HandleEvent merespons peristiwa.

Kode berikut menunjukkan metode Panggil :

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

Metode Panggil melakukan langkah-langkah berikut:

  1. Panggil IMFMediaEventGenerator::EndGetEvent untuk mendapatkan peristiwa. Metode ini mengembalikan penunjuk ke antarmuka IMFMediaEvent .
  2. Panggil IMFMediaEvent::GetType untuk mendapatkan kode peristiwa.
  3. Jika kode peristiwa adalah MESessionClosed, panggil SetEvent untuk mengatur peristiwa m_hCloseEvent . Alasan untuk langkah ini dijelaskan di Langkah 7: Matikan Sesi Media, dan juga di komentar kode.
  4. Untuk semua kode peristiwa lainnya, panggil IMFMediaEventGenerator::BeginGetEvent untuk meminta peristiwa berikutnya.
  5. Posting pesan WM_APP_PLAYER_EVENT ke jendela.

Kode berikut menunjukkan metode HandleEvent, yang dipanggil ketika aplikasi menerima pesan 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;
}

Metode ini memanggil IMFMediaEvent::GetType untuk mendapatkan jenis peristiwa dan IMFMediaEvent::GetStatus untuk mendapatkan keberhasilan kode kegagalan yang terkait dengan peristiwa tersebut. Tindakan berikutnya yang diambil tergantung pada kode peristiwa.

MESessionTopologyStatus

Peristiwa MESessionTopologyStatus menandakan perubahan status topologi. Atribut MF_EVENT_TOPOLOGY_STATUS objek peristiwa berisi status . Untuk contoh ini, satu-satunya nilai yang menarik adalah MF_TOPOSTATUS_READY, yang menunjukkan bahwa pemutaran siap dimulai.

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

Metode CPlayer::StartPlayback ini ditampilkan di Langkah 6: Kontrol Pemutaran.

Contoh ini juga memanggil MFGetService untuk mendapatkan antarmuka IMFVideoDisplayControl dari Enhanced Video Renderer (EVR). Antarmuka ini diperlukan untuk menangani pengecatan ulang dan mengubah ukuran jendela video, juga ditampilkan di Langkah 6: Kontrol Pemutaran.

MEEndOfPresentation

Peristiwa MEEndOfPresentation menandakan bahwa pemutaran telah mencapai akhir file. Sesi Media secara otomatis beralih kembali ke status berhenti.

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

Acara MENewPresentation menandakan dimulainya presentasi baru. Data peristiwa adalah penunjuk IMFPresentationDescriptor untuk presentasi baru.

Dalam banyak kasus, Anda tidak akan menerima peristiwa ini sama sekali. Jika Anda melakukannya, gunakan penunjuk IMFPresentationDescriptor untuk membuat topologi pemutaran baru, seperti yang ditunjukkan pada Langkah 3: Buka File Media. Kemudian antrekan topologi baru pada Sesi Media.

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

Berikutnya: Langkah 6: Mengontrol Pemutaran

Pemutaran Audio/Video

Cara Memutar File Media dengan Media Foundation