MFPlay 教學課程:視訊播放
[MFPlay 可用於需求一節中指定的作業系統。 它在後續版本中可能會變更或無法使用。 ]
本教學課程提供使用 MFPlay 播放視訊的完整應用程式。 它是以 SimplePlay SDK 範例為基礎。
本教學課程包含下列各節:
如需 MFPlay API 的詳細討論,請參閱使用 MFPlay 消費者入門。
規格需求
MFPlay 需要 Windows 7。
標頭和程式庫檔案
在您的專案中包含下列標頭檔:
#define WINVER _WIN32_WINNT_WIN7
#include <new>
#include <windows.h>
#include <windowsx.h>
#include <mfplay.h>
#include <mferror.h>
#include <shobjidl.h> // defines IFileOpenDialog
#include <strsafe.h>
#include <Shlwapi.h>
連結至下列程式碼程式庫:
- mfplay.lib
- shlwapi.lib
全域變數
宣告下列全域變數:
IMFPMediaPlayer *g_pPlayer = NULL; // The MFPlay player object.
MediaPlayerCallback *g_pPlayerCB = NULL; // Application callback object.
BOOL g_bHasVideo = FALSE;
這些變數將如下所示使用:
-
g_hwnd
-
應用程式視窗的控制碼。
-
g_bVideo
-
布林值,可追蹤影片是否現正播放。
-
g_pPlayer
-
IMFPMediaPlayer介面的指標。 這個介面是用來控制播放。
-
g_pCallback
-
IMFPMediaPlayerCallback介面的指標。 應用程式會實作此回呼介面,以從播放機物件取得通知。
宣告回檔類別
若要從播放機物件取得事件通知,應用程式必須實作 IMFPMediaPlayerCallback 介面。 下列程式碼會宣告實作 介面的類別。 唯一的成員變數是 m_cRef,它會儲存參考計數。
IUnknown方法會內嵌實作。 稍後會顯示 IMFPMediaPlayerCallback::OnMediaPlayerEvent方法的實作;請參閱實作回呼方法。
// Implements the callback interface for MFPlay events.
class MediaPlayerCallback : public IMFPMediaPlayerCallback
{
long m_cRef; // Reference count
public:
MediaPlayerCallback() : m_cRef(1)
{
}
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] =
{
QITABENT(MediaPlayerCallback, IMFPMediaPlayerCallback),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&m_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
ULONG count = InterlockedDecrement(&m_cRef);
if (count == 0)
{
delete this;
return 0;
}
return count;
}
// IMFPMediaPlayerCallback methods
IFACEMETHODIMP_(void) OnMediaPlayerEvent(MFP_EVENT_HEADER *pEventHeader);
};
宣告 SafeRelease 函式
在本教學課程中, SafeRelease 函式是用來釋放介面指標:
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
開啟媒體檔案
函 PlayMediaFile
式會開啟媒體檔案,如下所示:
- 如果 g_pPlayer 為 Null,函式會呼叫 MFPCreateMediaPlayer 來建立媒體播放機物件的新實例。 MFPCreateMediaPlayer的輸入參數包含回呼介面的指標,以及視訊視窗的控制碼。
- 若要開啟媒體檔案,函式會呼叫 IMFPMediaPlayer::CreateMediaItemFromURL,傳入檔案的 URL。 這個方法會以非同步方式完成。 完成時,會呼叫應用程式的 IMFPMediaPlayerCallback::OnMediaPlayerEvent 方法。
HRESULT PlayMediaFile(HWND hwnd, PCWSTR pszURL)
{
HRESULT hr = S_OK;
// Create the MFPlayer object.
if (g_pPlayer == NULL)
{
g_pPlayerCB = new (std::nothrow) MediaPlayerCallback();
if (g_pPlayerCB == NULL)
{
return E_OUTOFMEMORY;
}
hr = MFPCreateMediaPlayer(
NULL,
FALSE, // Start playback automatically?
0, // Flags
g_pPlayerCB, // Callback pointer
hwnd, // Video window
&g_pPlayer
);
}
// Create a new media item for this URL.
if (SUCCEEDED(hr))
{
hr = g_pPlayer->CreateMediaItemFromURL(pszURL, FALSE, 0, NULL);
}
// The CreateMediaItemFromURL method completes asynchronously.
// The application will receive an MFP_EVENT_TYPE_MEDIAITEM_CREATED
// event. See MediaPlayerCallback::OnMediaPlayerEvent().
return hr;
}
函 OnFileOpen
式會顯示一般檔案對話方塊,讓使用者能夠選取要播放的檔案。 IFileOpenDialog介面是用來顯示一般檔案對話方塊。 此介面是 Windows 殼層 API 的一部分;它是在 Windows Vista 中引進,以取代舊版 GetOpenFileName 函式。 使用者選取檔案之後, OnFileOpen
會呼叫 PlayMediaFile
以開始播放。
void OnFileOpen(HWND hwnd)
{
IFileOpenDialog *pFileOpen = NULL;
IShellItem *pItem = NULL;
PWSTR pwszFilePath = NULL;
// Create the FileOpenDialog object.
HRESULT hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen));
if (SUCCEEDED(hr))
{
hr = pFileOpen->SetTitle(L"Select a File to Play");
}
// Show the file-open dialog.
if (SUCCEEDED(hr))
{
hr = pFileOpen->Show(hwnd);
}
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
{
// User canceled.
SafeRelease(&pFileOpen);
return;
}
// Get the file name from the dialog.
if (SUCCEEDED(hr))
{
hr = pFileOpen->GetResult(&pItem);
}
if (SUCCEEDED(hr))
{
hr = pItem->GetDisplayName(SIGDN_URL, &pwszFilePath);
}
// Open the media file.
if (SUCCEEDED(hr))
{
hr = PlayMediaFile(hwnd, pwszFilePath);
}
if (FAILED(hr))
{
ShowErrorMessage(L"Could not open file.", hr);
}
CoTaskMemFree(pwszFilePath);
SafeRelease(&pItem);
SafeRelease(&pFileOpen);
}
視窗訊息處理常式
接下來,宣告下列視窗訊息的訊息處理常式:
- WM_PAINT
- WM_SIZE
- WM_CLOSE
針對 WM_PAINT 訊息,您必須追蹤影片目前是否現正播放。 如果是,請呼叫 IMFPMediaPlayer::UpdateVideo 方法。 這個方法會使播放程式物件重新繪製最新的視訊畫面。
如果沒有影片,應用程式會負責繪製視窗。 在本教學課程中,應用程式只會呼叫 GDI FillRect 函式來填滿整個工作區。
void OnPaint(HWND hwnd)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (g_pPlayer && g_bHasVideo)
{
// Playback has started and there is video.
// Do not draw the window background, because the video
// frame fills the entire client area.
g_pPlayer->UpdateVideo();
}
else
{
// There is no video stream, or playback has not started.
// Paint the entire client area.
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
}
EndPaint(hwnd, &ps);
}
如需 WM_SIZE 訊息,請呼叫 IMFPMediaPlayer::UpdateVideo。 這個方法會使播放程式物件重新調整視訊,以符合視窗目前的大小。 請注意, UpdateVideo 同時用於 WM_PAINT 和 WM_SIZE。
void OnSize(HWND /*hwnd*/, UINT state, int /*cx*/, int /*cy*/)
{
if (state == SIZE_RESTORED)
{
if (g_pPlayer)
{
// Resize the video.
g_pPlayer->UpdateVideo();
}
}
}
針對 WM_CLOSE 訊息,釋放 IMFPMediaPlayer 和 IMFPMediaPlayerCallback 指標。
void OnClose(HWND /*hwnd*/)
{
SafeRelease(&g_pPlayer);
SafeRelease(&g_pPlayerCB);
PostQuitMessage(0);
}
實作 Callback 方法
IMFPMediaPlayerCallback介面會定義單一方法OnMediaPlayerEvent。 這個方法會在播放期間發生事件時通知應用程式。 方法會採用一個參數,這是 MFP_EVENT_HEADER 結構的指標。 結構的 eEventType 成員會指定發生的事件。
MFP_EVENT_HEADER結構後面可能會接著其他資料。 針對每個事件種類,會定義宏,以將 MFP_EVENT_HEADER 指標轉換成事件特定結構。 (請參閱 MFP_EVENT_TYPE.)
在本教學課程中,兩個事件很重要:
事件 | 描述 |
---|---|
MFP_EVENT_TYPE_MEDIAITEM_CREATED | CreateMediaItemFromURL完成時傳送。 |
MFP_EVENT_TYPE_MEDIAITEM_SET | SetMediaItem完成時傳送。 |
下列程式碼示範如何將 MFP_EVENT_HEADER 指標轉換成事件特定結構。
void MediaPlayerCallback::OnMediaPlayerEvent(MFP_EVENT_HEADER * pEventHeader)
{
if (FAILED(pEventHeader->hrEvent))
{
ShowErrorMessage(L"Playback error", pEventHeader->hrEvent);
return;
}
switch (pEventHeader->eEventType)
{
case MFP_EVENT_TYPE_MEDIAITEM_CREATED:
OnMediaItemCreated(MFP_GET_MEDIAITEM_CREATED_EVENT(pEventHeader));
break;
case MFP_EVENT_TYPE_MEDIAITEM_SET:
OnMediaItemSet(MFP_GET_MEDIAITEM_SET_EVENT(pEventHeader));
break;
}
}
MFP_EVENT_TYPE_MEDIAITEM_CREATED事件會通知應用程式IMFPMediaPlayer::CreateMediaItemFromURL方法已完成。 事件結構包含 IMFPMediaItem 介面的指標,代表從 URL 建立的媒體專案。 若要將專案排入佇列以供播放,請將此指標傳遞至 IMFPMediaPlayer::SetMediaItem 方法:
void OnMediaItemCreated(MFP_MEDIAITEM_CREATED_EVENT *pEvent)
{
// The media item was created successfully.
if (g_pPlayer)
{
BOOL bHasVideo = FALSE;
BOOL bIsSelected = FALSE;
// Check if the media item contains video.
HRESULT hr = pEvent->pMediaItem->HasVideo(&bHasVideo, &bIsSelected);
if (SUCCEEDED(hr))
{
g_bHasVideo = bHasVideo && bIsSelected;
// Set the media item on the player. This method completes
// asynchronously.
hr = g_pPlayer->SetMediaItem(pEvent->pMediaItem);
}
if (FAILED(hr))
{
ShowErrorMessage(L"Error playing this file.", hr);
}
}
}
MFP_EVENT_TYPE_MEDIAITEM_SET事件會通知應用程式SetMediaItem已完成。 呼叫 IMFPMediaPlayer::P lay 開始播放:
void OnMediaItemSet(MFP_MEDIAITEM_SET_EVENT * /*pEvent*/)
{
HRESULT hr = g_pPlayer->Play();
if (FAILED(hr))
{
ShowErrorMessage(L"IMFPMediaPlayer::Play failed.", hr);
}
}
實作 WinMain
在本教學課程的其餘部分中,不會呼叫媒體基礎 API。 下列程式碼顯示視窗程式:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
HANDLE_MSG(hwnd, WM_CLOSE, OnClose);
HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
HANDLE_MSG(hwnd, WM_SIZE, OnSize);
case WM_ERASEBKGND:
return 1;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
函 InitializeWindow
式會註冊應用程式的視窗類別,並建立視窗。
BOOL InitializeWindow(HWND *pHwnd)
{
const wchar_t CLASS_NAME[] = L"MFPlay Window Class";
const wchar_t WINDOW_NAME[] = L"MFPlay Sample Application";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = CLASS_NAME;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
RegisterClass(&wc);
HWND hwnd = CreateWindow(
CLASS_NAME, WINDOW_NAME, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, GetModuleHandle(NULL), NULL);
if (!hwnd)
{
return FALSE;
}
ShowWindow(hwnd, SW_SHOWDEFAULT);
UpdateWindow(hwnd);
*pHwnd = hwnd;
return TRUE;
}
最後,實作應用程式進入點:
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
HRESULT hr = CoInitializeEx(NULL,
COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr))
{
return 0;
}
HWND hwnd = NULL;
if (InitializeWindow(&hwnd))
{
// Message loop
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DestroyWindow(hwnd);
}
CoUninitialize();
return 0;
}
相關主題
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應