前々回、WPFを利用した動画再生のためのアプローチとして、MediaElementとMediaPlayerの二つがあり、前者はUIElementなのでWin32でいうコントロールのように使え、後者は描画機能として実装することを書きました。

 

今回は前々回紹介したダイアログにMediaElementを入れて、動画再生アプリサンプルとします。その為、既にコメント解説:で紹介したところは色付けせず、MediaElement関連の部分だけ青色(コメント解説赤色(MediaElement)紫色(MediaElementのメンバー)にします。見ていただければわかりますが、極めてシンプルでユーザーの手を付けるところは殆どありません。

 

【Test_MediaElement.cs】

////////////////////////////////////////
//MediaElement.cs - MediaElement Sample
// https://learn.microsoft.com/ja-jp/windows/communitytoolkit/controls/wpf-winforms/mediaplayerelement
// https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/graphics-multimedia/multimedia-overview?view=netframeworkdesktop-4.8
// http://www.wisdomsoft.jp/467.html

//解説:↑はMediaElementについての参照先です。
////////////////////////////////////////
using System;
using System.Windows;            //PresentationFramework.dllにある
using System.Windows.Controls;    //PresentationFramework.dllにある
using System.Windows.Media;        //PresentationCore.dllにある(MediaElement, Brushes構造体使用の為)
/* 参照DLL
RefFile=C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll
RefFile01=C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationCore.dll
RefFile02=C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\WindowsBase.dll
RefFile03=C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Xaml.dll
*/

namespace Test_MediaElement
{
    ///////////////////////////
    //エントリーポイントクラス
    ///////////////////////////
    class MainApp
    {
        [STAThread]
        public static void Main()
        {
            DlgWindow dlg = new DlgWindow();
            Application ap = new Application();    //明示的にインスタンスを作らなければならない
            ap.Run(dlg);
        }
    }

    public partial class DlgWindow : Window    //派生元が"Form"から"Window"となる
    {
        //コントロール
        private MediaElement me;
        private Button Btn1, Btn2, Btn3, Btn4, extBtn;
        //ファイル名
        string fName = "";
        //MediaElementコントロール用変数
        bool toggle = true;

        public DlgWindow()
        {
            this.Title = "MediaElement サンプル";
            this.Width = 640;
            this.Height = 480;
            this.WindowStyle = WindowStyle.ThreeDBorderWindow;
            this.ResizeMode = ResizeMode.NoResize;
            this.WindowState = WindowState.Normal;
            this.WindowStartupLocation = WindowStartupLocation.CenterScreen;    //Manual, CenterOwnerもある(Manualの場合の設定方法不明)
            this.Background = SystemColors.ControlBrush;
            InitControls();
        }

        public void InitControls()
        {
            //グリッド(Grid)と縦二列を作成
            Grid grid = new Grid();
            ColumnDefinition colL = new ColumnDefinition();
            ColumnDefinition colR = new ColumnDefinition();
            //列を定義
            //colL.Width = GridLength.Auto;
            colL.Width = new GridLength(540, GridUnitType.Pixel);
            colR.Width = GridLength.Auto;
            //colR.Width = new GridLength(80, GridUnitType.Pixel);
            grid.ColumnDefinitions.Add(colL);
            grid.ColumnDefinitions.Add(colR);

            //スタックパネル(StackPanel)を作成
            StackPanel stackPanel = new StackPanel();
            stackPanel.Width = 80;
            stackPanel.Height = 420;
            stackPanel.HorizontalAlignment = HorizontalAlignment.Right;

            //MediaElement
            me = new MediaElement();
            me.Margin = new Thickness(10, 10, 10, 10);    //構造体 Thickness(左, 上, 右, 下)
            me.LoadedBehavior = MediaState.Manual;    //解説:MediaElementの初期状態初期設定

            //グリッド左列にMediaElementを登録
            Grid.SetColumn(me, 0);    //解説:左の列を指定
            grid.Children.Add(me);    //解説:子UIElementに指定

            //ボタン1
            Btn1 = new Button();
            Btn1.Width = 60;
            Btn1.Height = 24;
            Btn1.HorizontalAlignment = HorizontalAlignment.Right;
            Btn1.VerticalAlignment = VerticalAlignment.Top;
            Btn1.Margin = new Thickness(10, 10, 10, 0);    //構造体 Thickness(左, 上, 右, 下)
            Btn1.Content = "Open";
            Btn1.Click += Button1_Click;
            stackPanel.Children.Add(Btn1);

            //ボタン2ボタン
            Btn2 = new Button();
            Btn2.Width = 60;
            Btn2.Height = 24;
            Btn2.HorizontalAlignment = HorizontalAlignment.Right;
            Btn2.VerticalAlignment = VerticalAlignment.Top;
            Btn2.Margin = new Thickness(10, 10, 10, 0);    //構造体 Thickness(左, 上, 右, 下)
            Btn2.Content = "Play";
            Btn2.Click += Button2_Click;
            stackPanel.Children.Add(Btn2);

            //ボタン3ボタン
            Btn3 = new Button();
            Btn3.Width = 60;
            Btn3.Height = 24;
            Btn3.HorizontalAlignment = HorizontalAlignment.Right;
            Btn3.VerticalAlignment = VerticalAlignment.Top;
            Btn3.Margin = new Thickness(10, 10, 10, 0);    //構造体 Thickness(左, 上, 右, 下)
            Btn3.Content = "Pause";
            Btn3.Click += Button3_Click;
            stackPanel.Children.Add(Btn3);

            //ボタン4ボタン
            Btn4 = new Button();
            Btn4.Width = 60;
            Btn4.Height = 24;
            Btn4.HorizontalAlignment = HorizontalAlignment.Right;
            Btn4.VerticalAlignment = VerticalAlignment.Top;
            Btn4.Margin = new Thickness(10, 10, 10, 0);    //構造体 Thickness(左, 上, 右, 下)
            Btn4.Content = "Stop";
            Btn4.Click += Button4_Click;
            stackPanel.Children.Add(Btn4);

            //終了ボタン
            extBtn = new Button();
            extBtn.Width = 60;
            extBtn.Height = 24;
            extBtn.HorizontalAlignment = HorizontalAlignment.Right;
            extBtn.VerticalAlignment = VerticalAlignment.Bottom;
            extBtn.Margin = new Thickness(10, 10, 10, 10);    //構造体 Thickness(左, 上, 右, 下)
            extBtn.Content = "終了";
            extBtn.Click += extBtn_Click;
            stackPanel.Children.Add(extBtn);

            //グリッド右列にスタックパネルを登録
            Grid.SetColumn(stackPanel, 1);
            grid.Children.Add(stackPanel);

            //ウィンドウにグリッドを登録
            this.Content = grid;
        }

        void Button1_Click(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();

            //解説:WPFの「ファイルを開く」ダイアログはこういう書き方になります。
            ofd.AddExtension = true;
            ofd.DefaultExt = "*.*";
            ofd.Filter = "Media(*.*)|*.*";
            ofd.ShowDialog();
            fName = ofd.FileName;    //解説:再生する動画ファイル名を記録します。
            //ofd.Dispose();    //WPFでは不要(https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/windows/how-to-open-common-system-dialog-box?view=netdesktop-8.0)
        }

        void Button2_Click(object sender, RoutedEventArgs e)
        {
            if(fName == "")    //解説:ファイルの登録がない場合
                MessageBox.Show("メディアファイルを選択してください。", "エラー", MessageBoxButton.OK, MessageBoxImage.Exclamation);    //解説:くどいですが、WinFormのMessageBoxButtons、MessageBoxIconとは違います。
            else
            {
                me.Source = new System.Uri(fName, UriKind.Absolute);    //解説:ソースファイルの登録
                me.Play();    //解説:再生します。
                Btn3.Content = "Pause";    //解説:ボタン3の表示を"Pause"にします。
                toggle = true;    //解説:トグルフラッグを真にします。
            }
        }

        void Button3_Click(object sender, RoutedEventArgs e)
        {
            if(toggle)    //解説:トグルフラッグが真なら
            {
                me.Pause();    //解説:一時中止します。
                Btn3.Content = "Resume";    //解説:ボタン3の表示を"Resume"にします。
            }
            else
            {
                me.Play();    //解説:動画再生を続けます。
                Btn3.Content = "Pause";    //解説:ボタン3の表示を"Pause"にします。
            }
            toggle = !toggle;    //解説:トグルフラッグを反転させます。
        }

        void Button4_Click(object sender, RoutedEventArgs e)
        {
            me.Stop();    //解説:動画再生を停止します。
            Btn3.Content = "Pause";    //解説:ボタン3の表示を"Pause"にします。
            toggle = true;    //解説:トグルフラッグを真にします。
        }

        void extBtn_Click(object sender, RoutedEventArgs e)
        {
            Close();    //解説:プログラムを閉じます。
        }
    }
}

 

これが「おっと!」(OOP-オブジェクトオリエンテッドプログラミング)の神髄ですね。MediaElementクラスのオブジェクトさえ作れば、後は最小限の動作(メソッド)で「動画再生、一時停止、中断」という目的が達成されます。Win32のDirectShowもC++でラッパークラス化すれば、同様にコントロールとしてインスタンスを作り、「動画再生、一時停止、中断」が簡単にできました。ご参考までにBCCSkeltonで自作(C++)したCDSHOWクラスの宣言部のみ後掲します。考えることは同じですね。

 

【CDSHOW.hの一部】

///////////////////////////
//CDSHOWクラス定義ファイル
///////////////////////////

#include <DShow.h>                    //DirectShowヘッダー
#include <wchar.h>

//フィルター定義(利用者のフィルターの環境によって適宜変更が必要)
#define    FILTER    "ビデオファイル(*.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:    //解説:「公開」メンバーはユーザーがアクセスしたり、メソッド(関数)として使用します。
    //ファイル名取得用変数
    char m_FileName[MAX_PATH];
    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(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();                    //ビデオウィンドウ、フィルターグラフの終了処理
};

 

次回はMediaElementと「一味違う」MediaPlayerの違いを解説してみましょう。