步骤 5:处理媒体会话事件
本主题是教程 如何使用 Media Foundation 播放媒体文件的步骤 5。 完整的代码显示在主题 媒体会话播放示例中。
有关本主题的背景信息,请阅读 媒体事件生成器。 本主题包含以下各节:
获取会话事件
为了从媒体会话获取事件,CPlayer 对象调用 IMFMediaEventGenerator::BeginGetEvent 方法,如 步骤 4:创建媒体会话所示。 此方法是异步的,这意味着它会立即返回到调用方。 发生下一个会话事件时,媒体会话将调用 CPlayer 对象的 IMFAsyncCallback::Invoke 方法。
请务必记住, Invoke 是从工作线程调用的,而不是从应用程序线程调用的。 因此, Invoke 的实现必须是多线程安全的。 一种方法是使用关键部分保护成员数据。 但是, CPlayer
类显示了一种替代方法:
- 在 Invoke 方法中,CPlayer 对象将 WM_APP_PLAYER_EVENT 消息发布到应用程序。 message 参数是 IMFMediaEvent 指针。
- 应用程序接收 WM_APP_PLAYER_EVENT 消息。
- 应用程序调用
CPlayer::HandleEvent
方法,传入 IMFMediaEvent 指针。 - 方法
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 方法执行以下步骤:
- 调用 IMFMediaEventGenerator::EndGetEvent 以获取事件。 此方法返回指向 IMFMediaEvent 接口的指针。
- 调用 IMFMediaEvent::GetType 以获取事件代码。
- 如果事件代码为 MESessionClosed,则调用 SetEvent 以设置 m_hCloseEvent 事件。 步骤 7:关闭媒体会话以及代码注释中解释了此步骤的原因。
- 对于所有其他事件代码,请调用 IMFMediaEventGenerator::BeginGetEvent 以请求下一个事件。
- 将 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:控制播放
相关主题