///////////////////////////
//CDSHOWクラス定義ファイル
///////////////////////////
#include <DShow.h>					//DirectShowヘッダー
#include <wWCHAR.h>

//フィルター定義（利用者のフィルターの環境によって適宜変更が必要）
#define	FILTER	L"ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;*.mid)\0*.mp3;*.wav;*.mid\0\0"

//親ウィンドウへの通知メッセージ
#define WM_GRAPHNOTIFY  WM_APP + 1

class CDSHOW {

protected:
	//DirectShow親ウィンドウ
	HWND m_hWnd;
	//DirectShow関連変数
	IGraphBuilder *m_pGraph;		//フィルターグラフビルダークラスポインター
	IMediaControl *m_pControl;		//メディアコントロールインターフェースポインター
	IMediaEventEx *m_pEvent;		//メディアイベントインターフェースポインター
	IVideoWindow *m_pVideoWindow;	//ビデオウィンドウポインター
	IMediaPosition *m_pMPos;		//メディア再生位置インターフェースポインター
	//結果判定用変数
	HRESULT m_hr;
public:
	//ファイル名用変数
	WCHAR m_WFileName[MAX_PATH];
	//ファイル再生時間変数（double）
	REFTIME m_Len;
	//メンバー関数
	CDSHOW();						//コンストラクター
	~CDSHOW();						//デストラクター
	void Init();					//メンバー関数の初期化
	bool Move(int, int, int, int);	//ビデオウィンドウの位置設定（x, y, w, h）
	bool SetFileName(WCHAR*, WCHAR*);	//ビデオファイル名を設定する
	bool Show(HWND);				//ビデオウィンドウの初期化とビデオの再生
	REFTIME GetDuration();			//ビデオ再生時間を返す
	OAFilterState GetState(int);	//フィルターグラフの再生状態を取得（0-Stopped、1-Paused、2-Running）
	bool Pause();					//ビデオの一時停止
	bool Continue();				//ビデオの再開
	void Stop();					//ビデオの中止
	REFTIME GetCurrentPos();		//現在の再生時間を返す
	bool SetCurrentPos(REFTIME);	//再生位置（時間−秒）を設定する
	bool IsOver(long&);				//親ウィンドウに置く終了モニタリング関数
	void CleanUp();					//ビデオウィンドウ、フィルターグラフの終了処理
};

//コンストラクター
CDSHOW::CDSHOW() {

	//ワイド文字使用のためロケールの初期化（日本語）
	setlocale(LC_CTYPE, L"JPN");
	//メンバー変数の初期化
	Init();
	//COMの初期化
	m_hr = CoInitialize(NULL);
	if(FAILED(m_hr))
		MessageBoxW(m_hWnd, L"COMを初期化できませんでした", L"エラー", MB_OK | MB_ICONERROR);
}

//デストラクター
CDSHOW::~CDSHOW() {

	//フィルターグラフの終了処理
	CleanUp();
	//COMの終了処理
	CoUninitialize();
}

//メンバー関数の初期化
void CDSHOW::Init() {

	//親ウィンド	ウハンドル
	m_hWnd = NULL;
	//DirectShow関連変数
	m_pGraph = NULL;
	m_pControl = NULL;
	m_pEvent = NULL;
	m_pVideoWindow = NULL;
	m_pMPos = NULL;
	m_Len = 0;
}

//ビデオウィンドウの位置設定
bool CDSHOW::Move(int x, int y, int w, int h) {

	//ビデオウィンドウが無ければ何もしない
	if(!m_pVideoWindow)
		return FALSE;
	if(m_pVideoWindow) {
		//描画先ウィンドウ内の描画位置の設定
		m_hr = m_pVideoWindow->SetWindowPosition(x, y, w, h);
	if(m_hr != S_OK && m_hr != E_NOINTERFACE) {	//VFW_E_NOT_CONNECTEDではなく、E_NOINTERFACEが返される
			MessageBoxW(m_hWnd, L"ビデオウィンドウの位置を設定できませんでした", L"エラー", MB_OK | MB_ICONERROR);
			return FALSE;
		}
		return TRUE;
	}
	else
		return FALSE;
}

//ビデオファイル名を設定する
bool CDSHOW::SetFileName(WCHAR* fn, WCHAR* ftr = FILTER) {

	if(*fn) {
		if(lstrlenW(fn) > MAX_PATH - 1) {
			MessageBox(m_hWnd, L"ファイルパス、名またはURLが長すぎます", L"エラー", MB_OK | MB_ICONERROR);
			*m_WFileName = L'\0';	//16bitの終端文字
			return FALSE;
		}
		else {
			//ファイル名の記録
			lstrcpyW(m_WFileName, fn);
			return TRUE;
		}
	}
	else {
		OPENFILENAMEW ofn;				//オープンファイルダイアログ構造体
		ZeroMemory(&ofn, sizeof(ofn));	//ゼロ初期化
		ofn.lStructSize		= sizeof(ofn);
		ofn.hwndOwner		= m_hWnd;
		ofn.lpstrFilter		= ftr;		//引数でフィルタリングする
		ofn.nFilterIndex	= 1;
		ofn.lpstrFile		= m_WFileName;
		ofn.nMaxFile		= MAX_PATH;
		ofn.lpstrTitle		= NULL;
		ofn.lpstrInitialDir = L".";
		ofn.Flags			= OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
		ofn.lpstrDefExt		= NULL;
		if(GetOpenFileName(&ofn)) 
			return TRUE;
		else {
			*m_WFileName = L'\0';	//16bitの終端文字
			return FALSE;
		}
	}
}

//ビデオウィンドウの初期化とビデオの再生
bool CDSHOW::Show(HWND hWnd) {

	//親ウィンドウの記録
	m_hWnd = hWnd;
	//フィルターグラフマネージャーを作成し、IGraphBuilderインターフェイスを取得
	m_hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
							IID_IGraphBuilder, (void **)&m_pGraph);
	if(FAILED(m_hr)) {
		MessageBoxW(m_hWnd, L"フィルターグラフマネージャーを作成できませんでした",
					L"エラー", MB_OK | MB_ICONERROR);
		goto exit;
	}
	//IGraphBuilderからIMediaControlインターフェイスを取得
	m_hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pControl);
	if(FAILED(m_hr)) {
		MessageBoxW(m_hWnd, L"メディアコントロールインターフェースを取得できませんでした",
					L"エラー", MB_OK | MB_ICONERROR);
		goto exit;
	}
	//IGraphBuilderからIMediaEventインターフェイスを取得
	m_hr = m_pGraph->QueryInterface(IID_IMediaEvent, (void **)&m_pEvent);
	if(FAILED(m_hr)) {
		MessageBoxW(m_hWnd, L"メディアイベントインターフェースを取得できませんでした",
					L"エラー", MB_OK | MB_ICONERROR);
		goto exit;
	}
	//指定されたファイルを再生できるフィルターグラフを作成する（存在しない場合、エラー）
	m_hr = m_pGraph->RenderFile(m_WFileName, NULL);
	if(FAILED(m_hr)) {
		MessageBoxW(m_hWnd, L"フィルターグラフを作成できませんでした", L"エラー", MB_OK | MB_ICONERROR);
		goto exit;
	}
	//再生位置インターフェースを取得する
	m_pGraph->QueryInterface(IID_IMediaPosition, (LPVOID *)&m_pMPos);
	//動画ファイルの長さ(秒−double）を取得
	m_pMPos->get_Duration(&m_Len);
	//親ウィンドウへの通知を開始する
	m_pEvent->SetNotifyWindow((OAHWND)m_hWnd, WM_GRAPHNOTIFY, 0);
	//ビデオコントロールを取得する
	m_hr = m_pGraph->QueryInterface(IID_IVideoWindow, (LPVOID*)&m_pVideoWindow);
	if(FAILED(m_hr)) {
		MessageBoxW(m_hWnd, L"ビデオウィンドウを取得できませんでした", L"エラー", MB_OK | MB_ICONERROR);
		goto exit;
	}
	//親ウィンドウの設定
	m_hr = m_pVideoWindow->put_Owner((OAHWND)m_hWnd);
	if(m_hr != S_OK && m_hr != E_NOINTERFACE) {	//VFW_E_NOT_CONNECTEDではなく、E_NOINTERFACEが返される
		MessageBoxW(m_hWnd, L"親ウィンドウを設定できませんでした", L"エラー", MB_OK | MB_ICONERROR);
		goto exit;
	}
	//ウィンドウスタイルを設定する
	m_hr = m_pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
	if(m_hr != S_OK && m_hr != E_NOINTERFACE) {	//VFW_E_NOT_CONNECTEDではなく、E_NOINTERFACEが返される
		MessageBoxW(m_hWnd, L"ウィンドウスタイルを設定できませんでした", L"エラー", MB_OK | MB_ICONERROR);
		goto exit;
	}
	//描画先ウィンドウを640 x 480の表示が行える最小サイズにする
	SetWindowPos(m_hWnd, HWND_TOP, 0, 0, 656, 541, SWP_NOMOVE);
	//サイズ変更が無い場合の為に親ウィンドウへWM_SIZEを送る
	SendMessageW(m_hWnd, WM_SIZE, 0, (LPARAM)MAKELONG(640, 480));
	//指定したウィンドウへIVideoWindowの通知を流す
	m_hr = m_pVideoWindow->put_MessageDrain((OAHWND)m_hWnd);
	//ウィンドウを表示する
	m_hr = m_pVideoWindow->put_Visible(OATRUE);
	if(m_hr != S_OK && m_hr != E_NOINTERFACE) {	//VFW_E_NOT_CONNECTEDではなく、E_NOINTERFACEが返される
		MessageBoxW(m_hWnd, L"ビデオウィンドウを表示できませんでした", L"エラー", MB_OK | MB_ICONERROR);
		goto exit;
	}
	//動画表示を開始する。
	m_hr = m_pControl->Run();
	if(SUCCEEDED(m_hr))
		return TRUE;
	//以上が正常終了で、エラーがあると以下を実行する
exit:
	CleanUp();
	return FALSE;
}

//ビデオ再生時間を返す
REFTIME CDSHOW::GetDuration() {

	m_pMPos->get_Duration(&m_Len);
	return m_Len;
}

//フィルターグラフの再生状態を取得（0-Stopped、1-Paused、2-Running）
OAFilterState CDSHOW::GetState(int delay) {

	OAFilterState fs = 0;
	if(m_pControl)
		m_pControl->GetState(delay, &fs);	//処理遅延の為、delay（ミリ秒）後に取得
	return fs;
}

//ビデオの一時停止
bool CDSHOW::Pause() {

	//動画表示を一時停止する。
	m_hr = m_pControl->Pause();
	if(SUCCEEDED(m_hr))
		return TRUE;
	else
		return FALSE;
}

//ビデオの再開
bool CDSHOW::Continue() {
	//動画表示を一時停止する。
	m_hr = m_pControl->Run();
	if(SUCCEEDED(m_hr))
		return TRUE;
	else
		return FALSE;
}

//ビデオの中止
void CDSHOW::Stop() {

	//動画表示を中止し、終了処理を行う
	CleanUp();
}

//現在の再生時間を返す
REFTIME CDSHOW::GetCurrentPos() {

	REFTIME tm;
	m_pMPos->get_CurrentPosition(&tm);
	return tm;
}

//再生位置（時間−秒）を設定する
bool CDSHOW::SetCurrentPos(REFTIME tm) {

	m_pMPos->put_CurrentPosition(tm);
}

//親ウィンドウに置く終了モニタリング関数
bool CDSHOW::IsOver(long& evtCode) {

	//IMediaEvent *m_pEventが設定されていなければFALSEで返る
	if(!m_pEvent)
		return FALSE;
	//イベント処理
	LONG_PTR Param1, Param2;
	while(SUCCEEDED(m_pEvent->GetEvent(&evtCode, &Param1, &Param2, 0))) {
		m_pEvent->FreeEventParams(evtCode, Param1, Param2);
		switch(evtCode) {
		case EC_COMPLETE:	//再生終了
		case EC_USERABORT:	//再生中止
		case EC_ERRORABORT:	//エラー
			CleanUp();
			return TRUE;
		}
	}
	return FALSE;
}

//ビデオウィンドウ、フィルターグラフの終了処理
void CDSHOW::CleanUp() {

	//メモリーの開放
	if(m_pVideoWindow) {
		//表示ウィンドウを消す
		//m_pVideoWindow->put_Visible(OAFALSE);
		//親をデスクトップとする
		//m_pVideoWindow->put_Owner(NULL);
		m_pVideoWindow->Release();
	}
	if(m_pMPos)
		m_pMPos->Release();
	if(m_pControl) {
		//動画表示を中止する。
		m_pControl->Stop(); 
		m_pControl->Release();
	}
	if(m_pEvent) {
		//親ウィンドウへの通知を終了する
		m_pEvent->SetNotifyWindow((OAHWND)NULL, 0, 0);
		m_pEvent->Release();
	}
	if(m_pGraph)
		m_pGraph->Release();
	//再初期化
	Init();
}
