2020年5月4日月曜日

WPFでViewModelからWindowをCloseさせる

View層からのCloseは、this.Close()とかでOKだけど、WPFなのでViewModel層から画面を閉じたいことって結構あります。
今回は「Windowがアクティブじゃない場合」についても考慮しています。

1.引数としてWindowを受け取る

コマンドのバインド時にCommandParameterにWindowを渡します。
例)画面Load時のイベントにWindowを渡す場合

<i:Interaction.Triggers>
  <i:EventTrigger EventName="Loaded">
  <i:InvokeCommandAction
      Command="{Binding LoadedCommand}"
      CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
  </i:EventTrigger>
</i:Interaction.Triggers>


ViewModelは
private void Loaded(Window window)
{
  ・・・
  window.close()
}


のように画面を閉じることができます。
個人的にはViewModelでControl触るのはあまり好きじゃないなぁ

2.Closeメッセージを送る

こっちの方がMVVMぽいかな。Livetを使用しています。
XAML側にCLOSEメッセージ受信用のトリガーを書きます

<i:Interaction.Triggers>
  <l:InteractionMessageTrigger MessageKey="Close" Messenger="{Binding Messenger, Mode=OneWay}">
  <l:WindowInteractionMessageAction InvokeActionOnlyWhenWindowIsActive="False"/>
  </l:InteractionMessageTrigger>
</i:Interaction.Triggers>

private void Loaded(Window window)
{
  ・・・
  Messenger.Raise(new WindowActionMessage(WindowAction.Close, "Close"));
}
※メッセージを使用する場合、Windowがアクティブではない場合、デフォルトでは処理が動きません。
通常の「閉じるボタンクリック」などでは問題ないですが「処理が終わったら画面を閉じる」のような処理の場合は、Windowが前面に出ていない可能性が結構あると思います。
InvokeActionOnlyWhenWindowIsActive="False"
を設定することで、アクティブ状態でなくてもメッセージを受け取ることができます。

2020年5月3日日曜日

C#からFFmpegを制御する

前回、FFmpegからRtmp配信をする方法について記載しました。

コマンドプロンプトからFFmpegを使用しての動画の変換やダウンロードであれば、あまり気にする必要ないかもしれませんがWPFなどから起動する場合はProcessクラスを使用します。

1.プログラムからFFmpegを起動する

まずは基本形
public void RunFfmpeg()
{
    var proc = new Process();
    proc.StartInfo.FileName = "FFmpeg.exeのパス";
    proc.StartInfo.Arguments = "FFmpegのオプション指定";
    proc.StartInfo.CreateNoWindow = true; // ウィンドウ表示しない
    proc.StartInfo.UseShellExecute = false;
    proc.Start();
}

こんな感じ。
CreateNoWindow = true
を指定すると、ウィンドウが表示されません。

2.FFmpegを停止する

放っておいて停止するような場合は構いませんが、ライブ配信を行うような場合は、終了処理が必要となります。

public void RunFfmpeg()
{
    var proc = new Process();
    proc.StartInfo.FileName = "FFmpeg.exeのパス";
    proc.StartInfo.Arguments = "FFmpegのオプション指定";
    proc.StartInfo.RedirectStandardInput = true;
    proc.StartInfo.CreateNoWindow = true; // ウィンドウ表示しない
    proc.StartInfo.UseShellExecute = false;
    proc.Start();
}

今回はRedirectStandardInput = trueを指定しています。
これで、プロセスに対してコマンドの送付が可能となります。
FFmpegは「Q」キーで停止可能となっています。
なので、停止指示は「Q」を送付すればOK

public void StopFfmpeg()
{
    StreamWriter inputWriter = proc.StandardInput;
    inputWriter.WriteLine("q");
    rtmpProc.WaitForExit(3000);
}


3.標準入力を使用して入力データを送る

今度はWEBカメラからの映像の取り込み動画に変換します。

public void RunFfmpeg()
{
    var proc = new Process();
    proc.StartInfo.FileName = "FFmpeg.exeのパス";
    proc.StartInfo.Arguments = "-i - -f image2pipe output.mp4";
    proc.StartInfo.RedirectStandardInput = true;
    proc.StartInfo.CreateNoWindow = true; // ウィンドウ表示しない
    proc.StartInfo.UseShellExecute = false;
    proc.Start();

}

Arguments の「-i -」を指定することで、StandardInputからの入力を受け付けるようになります。
また、フォーマットでは「image2pipe 」を指定します。

次にカメラからの画像の取り込みについて。
こちらのサイトではWEBカメラを使用した画像の取り込みを行っています。
C#によるUSBカメラ操作(WPF編)

画像の取り込みには、Accord.Video.DirectShowのVideoCaptureDeviceを使用します。

public event NewFrameEventHandler NewFrameGot = delegate { };

public void StartCamera()
{
    var device = new VideoCaptureDevice("カメラ名");
    device.NewFrame += NewFrameGot;
    device.Start();
}

こんな感じで、NewFrameイベントを使用します。
// コンストラクタで指定
NewFrameGot += CamDeviceCtrlNewFrameGot;

private void CamDeviceCtrlNewFrameGot(object sender, NewFrameEventArgs eventArgs)
{
    eventArgs.Frame.Save(proc.StandardInput.BaseStream, ImageFormat.Bmp);
}

WPFで動画配信の再生(Vlc.DotNet.Wpf)

この記事ではRTMPの受信、再生をする方法として記載していますが、RTMPだけでなくHLSなどライブストリーム配信について同じ方法で対応可能かなと思います。


前回記事ではFFMpegを使用したRTMPの配信方法について記載しました。

WPFだろうが何だろうが、C#ではProcessとしてFFmpegを起動してあげれば配信可能なので、簡単かなと思います。

問題なのは再生の方かな。

1.再生用のWebページを作って、そのページをWPFで開く

サーバに配信用のPlayerとHTMLを置いて、そのページをWebBrowserコントロールで表示しようと思いました。
これなら、WPF側では制御いらないのでいいのかなと・・・

PlayerとしてはHTML5のvideoタグの利用をする方法と、FlowPlayerを利用する方法を試してみました。
WebBrowserコントロールはそのままではHTML5が表示できないので、レジストリを修正。
WPFのWebBrowserでHTML5を表示する

単体では動かせるけど、webBrowserコントロールで表示するとなると、動いてくれません。
やはり、javaやらflashなどを含めたページだから厳しいのかな・・・?

2.VlcControlの使用

結局うまくいったのは、Vlc.DotNet.Wpfを使用する方法です。

こちらのサイトを参考に導入しました。



必要なものは以下の4つ。
NuGetから取得できます。

  • VideoLAN.LibVLC.Windows
  • Vlc.DotNet.Core
  • Vlc.DotNet.Core.Interops
  • Vlc.DotNet.Wpf


VlcPlayerはXAMLのコードビハインドで以下のように書くだけ。

VlcLibDirectory = new DirectoryInfo(System.IO.Path.Combine(Directory.GetCurrentDirectory(), "libvlc", IntPtr.Size == 4 ? "win-x86" : "win-x64"));
string StreamParams = ":network-caching=2000";

var player = new VlcControl();
container.Content = player;
player.SourceProvider.CreatePlayer(VlcLibDirectory);
player.SourceProvider.MediaPlayer.Play(new Uri(RTMPのアドレス), StreamParams);

作成したVlcPlayerをContentControlなどのContentに入れてあげればOK。
導入は簡単だと思います。

残念な点があるとすれば、操作が基本的にXamlのコードビハインドになること。
そこで、ViewModelからバインドできるようにアレンジします。

3.VlcControlをViewModelから使用する

VlcContorlを操作するため、UserControlを作成し依存プロパティを追加します。

Xamlはこんな感じ。余計なものは省略しています。

VLCPlayer.xaml
<UserControl>
  <Grid>
    <ContentControl Name="PlayerContainer" />
  </Grid>
<UserControl>

で、UserControlのコードビハインド。
この例では依存プロパティとして「StreamPath」プロパティを追加しています。
StreamPathにViewModelからRTMPのパスを指定することで、再生先を切り替え出来ます。

VLCPlayer.xaml.cs

    public partial class VLCPlayer : UserControl
    {
        // Extra parameters to pass to the viewer media. I found a 2 seconds buffer cache makes playing much more stable.
        private const string StreamParams = ":network-caching=2000";

        public VlcControl vlcPlayer;
        VlcLibDirectory = new DirectoryInfo(System.IO.Path.Combine(Directory.GetCurrentDirectory(), "libvlc", IntPtr.Size == 4 ? "win-x86" : "win-x64"));

        #region StreamPath
        public static readonly DependencyProperty StreamPathProperty =
        DependencyProperty.Register("StreamPath",
                                    typeof(string),
                                    typeof(VLCPlayer),
                                    new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(OnStreamPathChanged)));

        public string StreamPath
        {
            get { return (string)GetValue(StreamPathProperty); }
            set { SetValue(StreamPathProperty, value); }
        }

        private static void OnStreamPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            VLCPlayer ctrl = obj as VLCPlayer;
            if (ctrl == null) return;

            ctrl.SetCamera();
        }
        #endregion


        public VLCPlayer()
  {
   InitializeComponent();
  }

        public void SetCamera()
        {
            if (vlcPlayer != null)
            {
                vlcPlayer.Dispose();
                vlcPlayer = null;
            }

            if (string.IsNullOrEmpty(this.StreamPath)) return;

            vlcPlayer = AddPlayer(this.StreamPath, PlayerContainer);
        }


        private VlcControl AddPlayer(string streamPath, ContentControl container)
        {
            var player = new VlcControl();

            container.Content = player;

            player.SourceProvider.CreatePlayer(VlcLibDirectory);
            player.SourceProvider.MediaPlayer.Play(new Uri(streamPath), StreamParams);

            return player;
        }

        private void UserControl_Unloaded(object sender, RoutedEventArgs e)
        {
            try
            {
                if (vlcPlayer != null)
                {
                    vlcPlayer.Dispose();
                    vlcPlayer = null;
                }
            }
            catch (Exception ex) { }
        }
    }



2020年5月2日土曜日

WPFで依存プロパティでエラーが発生する。(xdg0012 )

WPFでユーザコントロールに依存プロパティ(DependencyProperty)を追加しました。
追加方法がいろんなサイトで紹介されているので割愛。

下のように「IsMute」プロパティを追加したのですが、エラーになってしまいます。


xdg0012 ’IsMute’は認識されないかアクセスできません

症状は以下の感じ。

  • デザイナが表示されない
  • ビルドすると動作する
  • リビルドしても解消しない
  • VisualStudio2019で発生
依存プロパティは慣れていないとミスすることも多いと思うけど、どう書き換えてもエラーが消えない・・・


いろいろ試しましたが、結果的にVisualStudioの問題だったみたい。
更新すると発生しなくなりました。