// Unicode文字セット
#pragma comment(lib, "strmiids.lib")
#include <DShow.h>
#include <evr.h>
#include <CommCtrl.h>
#include "resource.h"
#define SAFE_RELEASE(x) { if (x) { x->Release(); x = NULL; } }
#define LTRB(rect) rect.left, rect.top, rect.right, rect.bottom
#define CLASS_NAME TEXT("TestPlayer")
#define WINDOW_NAME TEXT("TestPlayer")
#define PANEL_H 32 // パネルの高さ
#define THUMB_W 16 // つまみの幅
#define TIMER1 1 // シーク時間定期描画
#define TIMER2 2 // コントロール遅延描画
#define WM_GRAPHNOTIFY (WM_APP + 1)
enum Ctrl {VIDEO, PLAY, STOP, SEEK, MESSAGE, VOLUME, PANEL, CTRL_NUM};
enum State {NO_FILE, STOPPED, PAUSED, RUNNING};
// 関数プロトタイプ宣言
HWND CreateWnd(HINSTANCE hInstance, int nCmdShow);
void Trace(LPCTSTR format, ...);
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnTimer(HWND hWnd, WPARAM wParam);
void UpdateSeek(HWND hWnd);
void DrawSeek(HDC hdc);
BOOL OnCommand(HWND hWnd, WPARAM wParam);
void SeekFrame(HWND hWnd, WORD wID);
void OnGraphNotify(HWND hWnd);
void OnLButtonDown(HWND hWnd, LPARAM lParam);
void OnLButtonUp(HWND hWnd, LPARAM lParam);
int HitTest(POINT pt);
void OnPlay(HWND hWnd);
void OnStop(HWND hWnd);
void StartTimer(HWND hWnd);
void StopTimer(HWND hWnd);
void OnPaint(HWND hWnd);
void OnSize(HWND hWnd, WPARAM wParam, LPARAM lParam);
void OnDropFiles(HWND hWnd, WPARAM wParam);
void OnCreate(HWND hWnd);
void InitGraph(HWND hWnd);
void ReleaseGraph(HWND hWnd);
HRESULT OpenFile(HWND hWnd, LPCWSTR pszFileName);
HRESULT InitEvr(HWND hWnd);
void AdjustWnd(HWND hWnd, int nMode);
// 外部変数構造体
static struct {
TCHAR szPath[MAX_PATH]; // 動画ファイルのフルパス
TCHAR szFile[MAX_PATH]; // 動画ファイル名
RECT rc[CTRL_NUM]; // コントロール領域
BOOL bMouseCap; // マウスキャプチャフラグ
int nCtrl; // クリックされたコントロール
State state; // 状態フラグ
UINT_PTR uIDEvent; // タイマID
int nRange; // シーク範囲
LONGLONG llCurr; // 動画の現在時間
LONGLONG llEnd; // 動画の終了時間
TCHAR szEnd[11+1]; // 動画の終了時間 00:00:00.00
SIZE size; // 動画の幅と高さ
IGraphBuilder *pGraph;
IMediaControl *pControl;
IMediaSeeking *pSeek;
IMediaEventEx *pEvent;
IMFVideoDisplayControl *pVideo;
} g;
//==============================================================================
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
MSG msg;
HACCEL hAccTable;
LPTSTR pszCmdLine;
LPTSTR *argv;
int argc;
HRESULT hr;
// COMライブラリの初期化
hr = CoInitialize(NULL);
if (FAILED(hr)) {
return 0;
}
msg.wParam = 0; // ウィンドウ作成失敗時の戻り値
g.size.cx = 640;
g.size.cy = 480;
// コマンド引数の取得
pszCmdLine = GetCommandLine();
argv = CommandLineToArgvW(pszCmdLine, &argc);
if (2 <= argc) {
wcscpy_s(g.szPath, argv[1]);
}
// ウィンドウの作成
hWnd = CreateWnd(hInstance, nCmdShow);
if (hWnd == NULL) {
goto Exit;
}
// メッセージループ
hAccTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_MAIN));
while (GetMessage(&msg, NULL, 0, 0)) {
if (TranslateAccelerator(msg.hwnd, hAccTable, &msg) == 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Exit:
CoUninitialize();
return msg.wParam;
}
//------------------------------------------------------------------------------
HWND CreateWnd(HINSTANCE hInstance, int nCmdShow)
{
WNDCLASSEX wcx;
HWND hWnd;
// ウィンドウクラスの登録
ZeroMemory(&wcx, sizeof wcx);
wcx.cbSize = sizeof wcx;
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = MainWndProc;
wcx.hInstance = hInstance;
wcx.hCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
wcx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wcx.lpszClassName = CLASS_NAME;
if (RegisterClassEx(&wcx) == 0) {
return NULL;
}
// ウィンドウの作成
hWnd = CreateWindowEx(
WS_EX_ACCEPTFILES,
CLASS_NAME, WINDOW_NAME,
WS_OVERLAPPEDWINDOW,
// CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
100, 100, g.size.cx, g.size.cy,
NULL, NULL, hInstance, NULL);
if (hWnd) {
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
}
return hWnd;
}
//------------------------------------------------------------------------------
void Trace(LPCTSTR format, ...)
{
va_list arg_ptr;
TCHAR buffer[256];
int size;
va_start(arg_ptr, format);
size = _vsnwprintf_s(buffer, _TRUNCATE, format, arg_ptr);
va_end(arg_ptr);
OutputDebugString(buffer);
if (size < 0) {
OutputDebugString(L"...\n");
}
}
//==============================================================================
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_TIMER:
OnTimer(hWnd, wParam);
break;
case WM_COMMAND:
if (OnCommand(hWnd, wParam) == FALSE) {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
break;
case WM_GRAPHNOTIFY:
OnGraphNotify(hWnd);
break;
case WM_LBUTTONDOWN:
OnLButtonDown(hWnd, lParam);
break;
case WM_LBUTTONUP:
OnLButtonUp(hWnd, lParam);
break;
case WM_PAINT:
OnPaint(hWnd);
break;
case WM_SIZE:
OnSize(hWnd, wParam, lParam);
break;
case WM_DROPFILES:
OnDropFiles(hWnd, wParam);
DragFinish((HDROP)wParam);
break;
case WM_CREATE:
OnCreate(hWnd);
break;
case WM_DESTROY:
ReleaseGraph(hWnd);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//------------------------------------------------------------------------------
void OnTimer(HWND hWnd, WPARAM wParam)
{
switch (wParam) {
case TIMER1:
if (g.pSeek) {
g.pSeek->GetCurrentPosition(&g.llCurr);
UpdateSeek(hWnd);
}
break;
case TIMER2:
InvalidateRect(hWnd, &g.rc[PANEL], TRUE);
KillTimer(hWnd, wParam);
break;
}
}
//------------------------------------------------------------------------------
void UpdateSeek(HWND hWnd)
{
HDC hdc;
hdc = GetDC(hWnd);
DrawSeek(hdc);
ReleaseDC(hWnd, hdc);
}
//------------------------------------------------------------------------------
void DrawSeek(HDC hdc)
{
HBRUSH hBrush;
RECT rc;
int nPos;
hBrush = GetSysColorBrush(COLOR_BTNFACE);
SelectObject(hdc, hBrush);
SetBkMode(hdc, TRANSPARENT);
Rectangle(hdc, LTRB(g.rc[SEEK]));
Rectangle(hdc, LTRB(g.rc[MESSAGE]));
if (g.state == NO_FILE) {
return;
}
// シークバー
nPos = (int)(g.nRange * g.llCurr / g.llEnd);
nPos += g.rc[SEEK].left;
SetRect(&rc, nPos, g.rc[SEEK].top, nPos + THUMB_W, g.rc[SEEK].bottom);
Rectangle(hdc, LTRB(rc));
// メッセージ
TCHAR szBuf[26+1];
int nCurr;
nCurr = (int)(g.llCurr / 100000LL);
swprintf_s(szBuf, L" %02d:%02d:%02d.%02d / %s",
nCurr / 360000, (nCurr / 6000) % 60, (nCurr / 100) % 60, nCurr % 100, g.szEnd);
DrawText(hdc, szBuf, -1, &g.rc[MESSAGE], DT_SINGLELINE | DT_VCENTER);
}
//------------------------------------------------------------------------------
BOOL OnCommand(HWND hWnd, WPARAM wParam)
{
WORD wID = LOWORD(wParam);
switch (wID) {
case ID_ENTER:
ShowWindow(hWnd, IsZoomed(hWnd) ? SW_SHOWNOACTIVATE : SW_MAXIMIZE);
break;
case ID_ESCAPE:
DestroyWindow(hWnd);
break;
case ID_PLAY:
OnPlay(hWnd);
break;
case ID_LEFT: case ID_LEFT_CTRL: case ID_LEFT_SHIFT:
case ID_RIGHT: case ID_RIGHT_CTRL: case ID_RIGHT_SHIFT:
SeekFrame(hWnd, wID);
break;
case ID_NUM1: case ID_NUM2: case ID_NUM3: case ID_NUM4:
AdjustWnd(hWnd, (wID - ID_NUM1) + 1);
break;
case ID_STOP:
OnStop(hWnd);
break;
default:
return FALSE;
}
return TRUE;
}
//------------------------------------------------------------------------------
// ポーズ: 3f Ctrl:1f Shift: 1s
// 再生時:10s Ctrl:5s Shift:60s
void SeekFrame(HWND hWnd, WORD wID)
{
static const LONGLONG llRunning[] = {
-100000000LL, -50000000LL, -600000000LL, 100000000LL, 50000000LL, 600000000LL
};
LONGLONG llFrame;
switch (g.state) {
case PAUSED:
switch (wID) {
case ID_LEFT:
llFrame = -3000000000LL / 2997LL; // 3f = 0.1001001s
break;
case ID_LEFT_CTRL:
llFrame = -1000000000LL / 2997LL; // 1s / 29.97f = 0.0333667s
break;
case ID_LEFT_SHIFT:
llFrame = -10000000LL;
break;
case ID_RIGHT:
llFrame = 3000000000LL / 2997LL;
break;
case ID_RIGHT_CTRL:
llFrame = 1000000000LL / 2997LL;
break;
case ID_RIGHT_SHIFT:
llFrame = 10000000LL;
break;
}
break;
case RUNNING:
llFrame = llRunning[wID - ID_LEFT];
break;
default:
return;
}
g.llCurr += llFrame;
g.llCurr = max(g.llCurr, 0LL);
g.llCurr = min(g.llCurr, g.llEnd);
g.pSeek->SetPositions(&g.llCurr, AM_SEEKING_AbsolutePositioning, NULL, 0);
UpdateSeek(hWnd);
}
//------------------------------------------------------------------------------
void OnGraphNotify(HWND hWnd)
{
long lEvCode, lParam1, lParam2;
Trace(L"OnGraphNotify\n");
if (g.pEvent == NULL) {
return;
}
while (SUCCEEDED(g.pEvent->GetEvent(&lEvCode, &lParam1, &lParam2, 0))) {
g.pEvent->FreeEventParams(lEvCode, lParam1, lParam2);
Trace(L"lEvCode=%#x\n", lEvCode);
switch (lEvCode) {
case EC_COMPLETE:
OnPlay(hWnd);
break;
}
}
}
//------------------------------------------------------------------------------
void OnLButtonDown(HWND hWnd, LPARAM lParam)
{
POINT pt;
SetCapture(hWnd);
g.bMouseCap = TRUE;
POINTSTOPOINT(pt, lParam);
g.nCtrl = HitTest(pt);
}
//------------------------------------------------------------------------------
void OnLButtonUp(HWND hWnd, LPARAM lParam)
{
POINT pt;
if (g.bMouseCap == FALSE) {
return;
}
ReleaseCapture();
g.bMouseCap = FALSE;
POINTSTOPOINT(pt, lParam);
if (HitTest(pt) != g.nCtrl) {
return;
}
switch (g.nCtrl) {
case PLAY:
OnPlay(hWnd);
break;
case STOP:
OnStop(hWnd);
break;
case SEEK:
{
int nPos;
if (g.state <= STOPPED) {
break;
}
nPos = (pt.x - g.rc[SEEK].left) - THUMB_W / 2;
nPos = max(nPos, 0);
nPos = min(nPos, g.nRange);
g.llCurr = g.llEnd * nPos / g.nRange;
g.pSeek->SetPositions(&g.llCurr, AM_SEEKING_AbsolutePositioning, NULL, 0);
UpdateSeek(hWnd);
}
break;
}
}
//------------------------------------------------------------------------------
int HitTest(POINT pt)
{
int n;
for (n = 0; n < CTRL_NUM; n++) {
if (PtInRect(g.rc + n, pt)) {
return n;
}
}
return -1;
}
//------------------------------------------------------------------------------
void OnPlay(HWND hWnd)
{
switch (g.state) {
case NO_FILE:
break;
case STOPPED:
case PAUSED:
g.pControl->Run();
g.state = RUNNING;
StartTimer(hWnd);
break;
case RUNNING:
g.pControl->Pause();
g.state = PAUSED;
StopTimer(hWnd);
g.pSeek->GetCurrentPosition(&g.llCurr);
break;
}
}
//------------------------------------------------------------------------------
void OnStop(HWND hWnd)
{
if (g.state <= STOPPED) {
return;
}
g.pControl->Stop();
g.state = STOPPED;
StopTimer(hWnd);
g.llCurr = 0LL;
g.pSeek->SetPositions(&g.llCurr, AM_SEEKING_AbsolutePositioning, NULL, 0);
UpdateSeek(hWnd);
}
//------------------------------------------------------------------------------
void StartTimer(HWND hWnd)
{
g.uIDEvent = SetTimer(hWnd, TIMER1, 1000, NULL);
}
//------------------------------------------------------------------------------
void StopTimer(HWND hWnd)
{
if (g.uIDEvent == 0) {
return;
}
KillTimer(hWnd, TIMER1);
g.uIDEvent = 0;
}
//------------------------------------------------------------------------------
void OnPaint(HWND hWnd)
{
HDC hdc;
PAINTSTRUCT ps;
Trace(L"OnPaint\n");
if (g.pVideo) {
g.pVideo->RepaintVideo();
}
hdc = BeginPaint(hWnd, &ps);
DrawSeek(hdc);
Rectangle(hdc, LTRB(g.rc[PLAY]));
DrawText(hdc, L"Play", -1, &g.rc[PLAY], DT_SINGLELINE | DT_VCENTER | DT_CENTER);
Rectangle(hdc, LTRB(g.rc[STOP]));
DrawText(hdc, L"Stop", -1, &g.rc[STOP], DT_SINGLELINE | DT_VCENTER | DT_CENTER);
Rectangle(hdc, LTRB(g.rc[VOLUME]));
DrawText(hdc, L"Volume", -1, &g.rc[VOLUME], DT_SINGLELINE | DT_VCENTER | DT_CENTER);
EndPaint(hWnd, &ps);
}
//------------------------------------------------------------------------------
void OnSize(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
POINTS pts;
RECT rc;
LONG lPanelT;
LONG lSeekB;
pts = MAKEPOINTS(lParam);
Trace(L"OnSize(%u, %d, %d)\n", wParam, pts.x, pts.y);
if (wParam == SIZE_MINIMIZED) {
return;
}
lPanelT = pts.y - PANEL_H;
SetRect(&g.rc[VIDEO], 0, 0, pts.x, lPanelT);
SetRect(&rc, 0, lPanelT, pts.x, pts.y);
lSeekB = rc.top + 12;
g.rc[PANEL] = rc;
SetRect(&g.rc[PLAY], 0, rc.top, 40, rc.bottom);
SetRect(&g.rc[STOP], 40, rc.top, 80, rc.bottom);
SetRect(&g.rc[SEEK], 80, rc.top, rc.right, lSeekB);
SetRect(&g.rc[MESSAGE], 80, lSeekB, rc.right - 80, rc.bottom);
SetRect(&g.rc[VOLUME], rc.right - 80, lSeekB, rc.right, rc.bottom);
g.nRange = (g.rc[SEEK].right - g.rc[SEEK].left) - THUMB_W;
// ビデオ出力位置のセット
if (g.pVideo) {
MFVideoNormalizedRect mvnr = {0.0f, 0.0f, 1.0f, 1.0f};
g.pVideo->SetVideoPosition(&mvnr, &g.rc[VIDEO]);
}
if (g.state == RUNNING) {
SetTimer(hWnd, TIMER2, 100, NULL);
}
}
//------------------------------------------------------------------------------
void OnDropFiles(HWND hWnd, WPARAM wParam)
{
HDROP hDrop = (HDROP)wParam;
ReleaseGraph(hWnd);
DragQueryFile(hDrop, 0, g.szPath, _countof(g.szPath));
InitGraph(hWnd);
}
//------------------------------------------------------------------------------
void OnCreate(HWND hWnd)
{
Trace(L"OnCreate\n");
if (g.szPath[0]) {
InitGraph(hWnd);
}
}
//------------------------------------------------------------------------------
void InitGraph(HWND hWnd)
{
TCHAR szBuf[280];
TCHAR szDrive [_MAX_DRIVE];
TCHAR szDir [_MAX_DIR];
TCHAR szFName [_MAX_FNAME];
TCHAR szExt [_MAX_EXT];
int nEnd;
HRESULT hr;
// DirectShowフィルタの準備
hr = OpenFile(hWnd, g.szPath);
if (FAILED(hr)) {
return;
}
g.state = STOPPED;
// タイトルバー
_wsplitpath_s(g.szPath, szDrive, szDir, szFName, szExt);
_wmakepath_s(g.szFile, NULL, NULL, szFName, szExt);
swprintf_s(szBuf, L"%s - %s", g.szFile, WINDOW_NAME);
SetWindowText(hWnd, szBuf);
// 停止タイムの取得
hr = g.pSeek->GetStopPosition(&g.llEnd);
nEnd = (int)(g.llEnd / 100000LL);
swprintf_s(g.szEnd, L"%02d:%02d:%02d.%02d",
nEnd / 360000, (nEnd / 6000) % 60, (nEnd / 100) % 60, nEnd % 100);
// 動画再生
OnPlay(hWnd);
}
//------------------------------------------------------------------------------
void ReleaseGraph(HWND hWnd)
{
StopTimer(hWnd);
if (g.pControl) {
g.pControl->Stop();
g.state = NO_FILE;
Sleep(500);
}
if (g.pEvent) {
g.pEvent->SetNotifyWindow(NULL, 0, 0);
}
SAFE_RELEASE(g.pVideo);
SAFE_RELEASE(g.pEvent);
SAFE_RELEASE(g.pSeek);
SAFE_RELEASE(g.pControl);
SAFE_RELEASE(g.pGraph);
}
//==============================================================================
HRESULT OpenFile(HWND hWnd, LPCWSTR pszFile)
{
// フィルタグラフの作成
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&g.pGraph));
// メディアコントロールインターフェイスの取得
if (SUCCEEDED(hr)) {
hr = g.pGraph->QueryInterface(IID_PPV_ARGS(&g.pControl));
}
// ビデオの作成
if (SUCCEEDED(hr)) {
hr = InitEvr(hWnd);
}
// グラフを作成する
if (SUCCEEDED(hr)) {
hr = g.pGraph->RenderFile(pszFile, NULL);
}
// シークインターフェイス
DWORD dwCaps;
if (SUCCEEDED(hr)) {
hr = g.pGraph->QueryInterface(IID_PPV_ARGS(&g.pSeek));
}
if (SUCCEEDED(hr)) {
hr = g.pSeek->GetCapabilities(&dwCaps); // 55 0x37 0011.0111
AM_SEEKING_CanSeekAbsolute;
}
if (SUCCEEDED(hr)) {
hr = g.pSeek->IsFormatSupported(&TIME_FORMAT_MEDIA_TIME);
// frame=false
}
// イベント
if (SUCCEEDED(hr)) {
hr = g.pGraph->QueryInterface(IID_PPV_ARGS(&g.pEvent));
}
if (SUCCEEDED(hr)) {
hr = g.pEvent->SetNotifyWindow((OAHWND)hWnd, WM_GRAPHNOTIFY, 0);
}
// 描画領域の設定
if (SUCCEEDED(hr)) {
g.pVideo->GetNativeVideoSize(&g.size, NULL);
}
if (SUCCEEDED(hr)) {
AdjustWnd(hWnd, 2);
}
return hr;
}
//------------------------------------------------------------------------------
HRESULT InitEvr(HWND hWnd)
{
IBaseFilter *pEvr = NULL;
// EVRの作成
HRESULT hr = CoCreateInstance(CLSID_EnhancedVideoRenderer, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pEvr));
// フィルタグラフにEVRを追加
if (SUCCEEDED(hr)) {
hr = g.pGraph->AddFilter(pEvr, L"EVR");
}
IMFGetService *pService = NULL;
if (SUCCEEDED(hr)) {
hr = pEvr->QueryInterface(IID_PPV_ARGS(&pService));
}
if (SUCCEEDED(hr)) {
hr = pService->GetService(MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&g.pVideo));
}
SAFE_RELEASE(pService);
if (SUCCEEDED(hr)) {
hr = g.pVideo->SetVideoWindow(hWnd);
}
SAFE_RELEASE(pEvr);
return hr;
}
//------------------------------------------------------------------------------
void AdjustWnd(HWND hWnd, int nMode)
{
RECT rc;
SetRect(&rc, 0, 0, g.size.cx * nMode / 2, g.size.cy * nMode / 2 + PANEL_H);
AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0);
SetWindowPos(hWnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top,
SWP_NOZORDER | SWP_NOMOVE);
}