今回はこのプログラムの核心、DirectShowのクラスCDSHOWを(解説:)します。(

注:このブログをアップした後、DirectShowのエラー発生時対応を改良した為、以下のファイルを2か所修正しました。一つはGetDuration()関数にREFTIMEのローカス変数を使っていましたが、メンバー変数のm_Lenにしたこと(というか、完全に忘れていました、この変数!)、もう一つはIsOver()関数が真偽(bool)の戻り値でしたが、正確な終了自由を取りたいユーザーがいるはずなので「参照型long引数(long&)」を与え、IsOver(evnt)のように使うことで、再生終了、再生中止、再生エラーの終了事由を取ることができるようにしました。以下は既に修正されています。

【CDSHOW.h】
///////////////////////////
//CDSHOWクラス定義ファイル
///////////////////////////
#include <DShow.h>                    //DirectShowヘッダー
//(解説:DirectShowを使う場合には、このヘッダーファイルを必ず読み込みます。)
#include <wchar.h>
//(解説:COMの文字列はワイド文字が標準であり、wchar.hは必ず読み込みます。)

//フィルター定義(利用者のフィルターの環境によって適宜変更が必要
#define    FILTER    "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;*.mid)\0*.mp3;*.wav;*.mid\0\0"
//(解説:これは私の利用したいファイル種類を列挙しただけで、DirectShowはこれ以上の対応幅があります。)

//親ウィンドウへの通知メッセージ
#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;
//(解説:内部で処理する関連の変数なのでprotectedとしました。)
public:
    //ファイル名取得用変数
    char m_FileName[MAX_PATH];
    WCHAR m_WFileName[MAX_PATH];
//(解説:ファイル名はユーザーからアクセスすることを許します。通常はマルチバイトのchar*を使います。)
    //ファイル再生時間変数(double)
    REFTIME m_Len;
//(解説:再生時間取得用で、REFTIMEはd実際はdoubleです。)
    //メンバー関数
    CDSHOW();                        //コンストラクター
    ~CDSHOW();                        //デストラクター
    void Init();                    //メンバー関数の初期化
    bool Move(int, int, int, int);    //ビデオウィンドウの位置設定(x, y, w, h)
    bool SetFileName(char*, char*);    //ビデオファイル名を設定する
    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, "JPN");
    //メンバー変数の初期化
    Init();
    //COMの初期化
    m_hr = CoInitialize(NULL);
    if(FAILED(m_hr))
        MessageBox(m_hWnd, "COMを初期化できませんでした", "エラー", MB_OK | MB_ICONERROR);
//(解説:マルチバイト文字のワイド文字変換にはロケールが不可欠です。メンバー変数の初期化はInit関数にまとめ、一回だけ行うCOM初期化も実行します。)
}

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

    //フィルターグラフの終了処理
    CleanUp();
//(解説:メモリー解放を行うべきものはCleanUpで纏めて実施します。)
    //COMの終了処理
    CoUninitialize();
//(解説:COMの必須終了処理です。)
}

//メンバー関数の初期化
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が返される
//(解説:例の「画像フィルターが不要でビデオウィンドウが作れてしまう場合」の仕様外のエラーメッセージが出る件です。)
            MessageBox(m_hWnd, "ビデオウィンドウの位置を設定できませんでした", "エラー", MB_OK | MB_ICONERROR);
            return FALSE;
        }
        return TRUE;
    }
    else
        return FALSE;
}

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

//(解説:DirectShowに読ませるワイド文字ファイルパス、名です。マルチバイト文字のファイルパス、名を変換します。)
    if(*fn) {
//(解説:ファイル名指定がある場合です。)
        if(lstrlen(fn) > MAX_PATH - 1) {
            MessageBox(m_hWnd, "ファイルパス、名またはURLが長すぎます", "エラー", MB_OK | MB_ICONERROR);
            *m_FileName = NULL;
            *m_WFileName = L'\0';    //16bitの終端文字
            return FALSE;
        }
        else {
            //ファイル名の記録
            lstrcpy(m_FileName, fn);
            //ファイル名のワイド文字化
            mbstowcs(m_WFileName, m_FileName, MAX_PATH);
            return TRUE;
        }
    }
    else {
//(解説:ファイル名指定が無い("")場合です。cmndlgを使わずWin32APIのGetOpenFileNameで書いてみました。)
        OPENFILENAME ofn;                //オープンファイルダイアログ構造体
        ZeroMemory(&ofn, sizeof(ofn));    //ゼロ初期化
        ofn.lStructSize        = sizeof(ofn);
        ofn.hwndOwner        = m_hWnd;
        ofn.lpstrFilter        = ftr;        //引数でフィルタリングする
        ofn.nFilterIndex    = 1;
        ofn.lpstrFile        = m_FileName;
        ofn.nMaxFile        = MAX_PATH;
        ofn.lpstrTitle        = NULL;
        ofn.lpstrInitialDir = ".";
        ofn.Flags            = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
        ofn.lpstrDefExt        = NULL;
        bool success = GetOpenFileName(&ofn);
        if(success) {
            //ファイル名のワイド文字化
            mbstowcs(m_WFileName, m_FileName, MAX_PATH);
            return TRUE;
        }
        else {
            *m_FileName = NULL;
            *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)) {
        MessageBox(m_hWnd, "フィルターグラフマネージャーを作成できませんでした",
                    "エラー", MB_OK | MB_ICONERROR);
        goto exit;

//(解説:多くのチェックがあるのでエラー処理をまとめて、久々のgotoを使いました。。)

    }

    //IGraphBuilderからIMediaControlインターフェイスを取得
    m_hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pControl);
    if(FAILED(m_hr)) {
        MessageBox(m_hWnd, "メディアコントロールインターフェースを取得できませんでした",
                    "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //IGraphBuilderからIMediaEventインターフェイスを取得
    m_hr = m_pGraph->QueryInterface(IID_IMediaEvent, (void **)&m_pEvent);
    if(FAILED(m_hr)) {
        MessageBox(m_hWnd, "メディアイベントインターフェースを取得できませんでした",
                    "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //指定されたファイルを再生できるフィルターグラフを作成する(存在しない場合、エラー)
    m_hr = m_pGraph->RenderFile(m_WFileName, NULL);
    if(FAILED(m_hr)) {
        MessageBox(m_hWnd, "フィルターグラフを作成できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
//(解説:特定のPC環境で再生できるフィルターが無い場合、ここでエラーとなります。)
    //再生位置インターフェースを取得する
    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)) {
        MessageBox(m_hWnd, "ビデオウィンドウを取得できませんでした", "エラー", 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が返される
//(解説:以降は例の「画像フィルターが不要でビデオウィンドウが作れてしまう場合」の仕様外のエラーメッセージが出る件です。FAILED()マクロは使えません。)
        MessageBox(m_hWnd, _T("親ウィンドウを設定できませんでした"), _T("エラー"), 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が返される
        MessageBox(m_hWnd, "ウィンドウスタイルを設定できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //描画先ウィンドウを640 x 480の表示が行える最小サイズにする
    SetWindowPos(m_hWnd, HWND_TOP, 0, 0, 656, 541, SWP_NOMOVE);
//(解説:クライアント領域サイズ640x480のウィンドウサイズはPCによって異なる可能性があります。)
    //サイズ変更が無い場合の為に親ウィンドウへWM_SIZEを送る
    SendMessage(m_hWnd, WM_SIZE, 0, (LPARAM)MAKELONG(640, 480));
//(解説:既に最小サイズになっている場合、SetWindowPosではWM_SIZEが出ないので、強制的に出します。)
    //指定したウィンドウへ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が返される
        MessageBox(m_hWnd, "ビデオウィンドウを表示できませんでした", "エラー", 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(ミリ秒)後に取得
//(解説:再生終了を監視する目的で使い、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;
//(解説:WaitForCompletion関数を使うとウィンドウが無効化するので使わず、ユーザー定義のWM_GRAPHNOTIFYメッセージを親に送ってこの関数で監視します。)
}

//ビデオウィンドウ、フィルターグラフの終了処理
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();
//(解説:「後入れ先出し」で、先にインターフェースからメモリーを解放して、親玉のグラフマネージャーを最後に開放します。全て解放したら、メンバー変数を初期化します。)
}

DirectShowは必要な処理をすべてメソッドで纏められているので、ユーザーのラッピングは非常に簡単です。これでメインウィンドウの処理が簡素化できます。

【CSLIDER.h】
/////////////////////////////////////////
//スライダー(CSLIDER)クラス定義ファイル
/////////////////////////////////////////

class CSLIDER : public CCTRL
{
public:
    //メンバー関数
    bool Create(HWND, int, int,
                int, int, int, DWORD);    //コントロール作成関数
    void AutoSize(int, int, int);        //親のサイズ変更に合わせて位置(0-3)を変更
    void SetSize(int, int, int, int);    //位置と大きさを変更する
    void SetRange(int, int);            //スライダーの範囲設定
    void SetPageSize(int);                //ページサイズ(マウスクリック時の移動量)設定
    void SetLineSize(int);                //ラインサイズ(矢印キーによる移動量)設定
    int  GetPosition();                    //ポジションの取得
    void SetPosition(int);                //ポジションの設定
    void SetTic(int);                    //目盛りサイズの設定
};
//(解説:大体何をするのかはコメントでわかると思います。)

//コントロール作成関数
bool CSLIDER::Create(HWND hParent, int cID, int x, int y, int w, int h,
                    DWORD Style = WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS) {

    m_hWnd = CreateWindowEx(m_dwExStyle = WS_EX_TOPMOST,
                            m_lpClassName = TRACKBAR_CLASS,
                            m_lpWindowName = "",
                            m_dwStyle = Style,
                            m_x = x,
                            m_y = y,
                            m_Width = w,
                            m_Height = h,
                            m_hParent = hParent,
                            m_hMenu = (HMENU)cID,
                            m_hInstance = (HINSTANCE)GetWindowLong(hParent, GWL_HINSTANCE),
                            m_lpParam = NULL
    );
    if(m_hWnd == NULL) {
        MessageBox(0, "スライダーコントロールを作れませんでした", "エラー",
                    MB_OK | MB_ICONEXCLAMATION);
        return FALSE;
    }
    //コントロールの表示と再描画
    ShowWindow(m_hWnd, SW_SHOWNORMAL);
    UpdateWindow(m_hWnd);
    return TRUE;
//(解説:スライダーコントロールの作成関数です。)
}

//親のサイズ変更に合わせて位置(時計回りに0-3の隅)を変更
void CSLIDER::AutoSize(int pos, int marginx = 0, int marginy = 0) {

    RECT rec;
    GetClientRect(m_hParent, &rec);
    switch(pos) {
    case 0:        //左上隅
        m_x = marginx;
        m_y = marginy;
        break;
    case 1:        //右上隅
        m_x = (rec.right - rec.left) - m_Width - marginx;
        m_y = marginy;
        break;
    case 2:        //右下隅
        m_x = (rec.right - rec.left) - m_Width - marginx;
        m_y = (rec.bottom - rec.top) - m_Height - marginy;
        break;
    case 3:        //左下隅
        m_x = marginx;
        m_y = (rec.bottom - rec.top) - m_Height - marginy;
        break;
    default:
        return;
    }
    MoveWindow(m_hWnd, m_x, m_y, m_Width, m_Height, TRUE);
//(解説:これは今回ツールバーの中に置くことを考えて作りましたが、他の応用も効くはずです。親ウィンドウの四隅にmarginx, yだけ離してスライダーを置きます。)
}

//位置と大きさを変更する
void CSLIDER::SetSize(int x, int y, int w, int h) {

    m_x = x;
    m_y = y;
    m_Width = w;
    m_Height = h;
//    SetWindowPos(m_hWnd, HWND_TOP, m_x, m_y, m_Width, m_Height, SWP_SHOWWINDOW);
    MoveWindow(m_hWnd, m_x, m_y, m_Width, m_Height, TRUE);
//(解説:AutoSizeでは高さ幅が決められないので、これも併用します。)
}

//スライダーの範囲設定
void CSLIDER::SetRange(int min, int max) {

    SendMessage(m_hWnd, TBM_SETRANGE, TRUE, (LPARAM)MAKELONG(min, max));
//(解説:まんま、です。)
}

//ページサイズ(マウスクリック時の移動量)設定
void CSLIDER::SetPageSize(int dst) {

    SendMessage(m_hWnd, TBM_SETPAGESIZE, 0, (LPARAM)dst);
//(解説:スライダーノブをつかまない場合の移動量です。)
}

//ラインサイズ(矢印キーによる移動量)設定
void CSLIDER::SetLineSize(int dst) {

    SendMessage(m_hWnd, TBM_SETLINESIZE, 0, (LPARAM)dst);
//(解説:キーの場合です。)
}

//ポジションの取得
int CSLIDER::GetPosition() {

    return SendMessage(m_hWnd, TBM_GETPOS, 0, 0);
//(解説:スライダーノブを移動させられた場合の位置取得用です。)
}

//ポジションの設定
void CSLIDER::SetPosition(int pos) {

    SendMessage(m_hWnd, TBM_SETPOS, TRUE, (LPARAM)pos);
//(解説:処理振興に合わせてスライダーを動かすために使います。)
}

//目盛りサイズの設定
void CSLIDER::SetTic(int tic) {

    //目盛りのサイズを設定 
    SendMessage(m_hWnd, TBM_SETTICFREQ, tic, 0);
//(解説:まんま、です。)
}

以上です。COMの扱いの定石をクラス化で省略することができ、簡単にCOMやスライダーコントロールが使えるようになります。