前々回、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の違いを解説してみましょう。