Vlc.DotNet.Wpfの罠にはまった話

最近Vlc.DotNet.Wpfを使っていてちょっと困ったのでメモ
#作っていたもの
VLC Media Playerでは複数のRTSPサーバからの映像を再生するのは一筋縄ではいきません。
なぜかというと、ウィンドウを複数立ち上げても同じポートで映像を受信しようとするからです。
同じポート番号でUDPを受信しようとしたらうまく受信できなくて当然ですね。

これはネットワークストリームを開くときに
[詳細オプションの表示]－[オプションの編集]に":rtp-client-port=<整数>"を指定してやり、
それぞれのウィンドウで別々の適切なポート番号を指定してやることで回避できます。

しかし、困ったことにこのオプション、内容を覚えておいてくれないし、しょっちゅう飛んでしまうので使いにくいことこの上ないです。
batなどからコマンドライン引数で指定してやって起動すればいいのですが、接続先ごとにbatを用意したりとなんともスマートではありません。

 **「使いにくいのなら自作ソフトに埋め込んでしまえVLC」** －詠み人知らず

#困った症状
そういうわけで、Visual Studioを入れたPCでせっせと開発していました。
で、あらかたデバッグも終わって、いざ実際にアプリケーションを使うPCで動作確認をしようとしたところ、動かない。
検証のため、必要最小限の ~~ウン~~ コードにしても動かない。
どう動かないかというと、最初の1フレームだけ表示して映像が再生されないんですね。
ファイルは開けているし、デコードもできてるじゃないか…そこまでできて何故…

動かない理由が謎過ぎて特に意味がないと思いますが、雑にスペック
開発環境PC：Windows 10 Enterprise 64 bit、デスクトップ、ネットワーク接続あり
本番環境PC:Windows 10 Pro 64 bit、ノート、スタンドアローン

いらないと思うけど以下ソース。そこらへんに転がっているサンプルコードと同じようなもんです。

```xml:MainWindow.xaml
<Window x:Class="VLCTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:VLCTest"
        xmlns:vlc="clr-namespace:Vlc.DotNet.Wpf;assembly=Vlc.DotNet.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
    <Grid>
        <vlc:VlcControl x:Name="player"/>
    </Grid>
</Window>
```

```csharp:MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace VLCTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            DirectoryInfo VlcLibDirectory = new DirectoryInfo(System.IO.Path.Combine(new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).DirectoryName, "libvlc", IntPtr.Size == 4 ? "win-x86" : "win-x64"));
            player.SourceProvider.CreatePlayer(VlcLibDirectory);
            FileInfo MediaSource1 = new FileInfo(System.IO.Path.Combine(new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).DirectoryName, "sample.mp4"));
            player.SourceProvider.MediaPlayer.EncounteredError += (sender, e) =>
             {
                 MessageBox.Show("An error occurred");
             };
            Task.Run(() =>
            {
                player.SourceProvider.MediaPlayer.SetMedia(MediaSource1);
                player.SourceProvider.MediaPlayer.Play();
            });
        }
    }
}

```

#困ったときには原点に立ち返れ（？）
さすがに症状的にビルドオプションだとかそういう類じゃなさそうだなとか、コーディングが悪い…可能性はあるだろうけど、サンプルコードも動かないとなるとさすがに…とかうんうん唸っていたのです。
困ったときには原点に立ち返れというわけで、Vlc.DotNetのGitHubページを見てみます。

> As more and more effort is put into LibVLCSharp, fewer evolutions are expected to be made to this project.
> ―[GitHub - ZeBobo5/Vlc.DotNet](https://github.com/ZeBobo5/Vlc.DotNet)

**おっ なんか「より新しいの使った方がええで」とか書いてあるやんけ**

#LibVLCSharpを使えば解決じゃ！
そういうわけで、ZeBobo5氏推奨のとおりLibVLCSharpを使ってみましょう。
##インストール
NuGetで以下のものを入れます。

* [LibVLCSharp.WPF](https://www.nuget.org/packages/LibVLCSharp.WPF/)
* [VideoLAN.LibVLC.Windows](https://www.nuget.org/packages/VideoLAN.LibVLC.Windows/)

LibVLCSharpはLibVLCSharp.WPFを入れれば勝手に入るはず。
##使い方
私にもわかる程度なのでそんなに難しくはないのですが、LibVLCSharpの使い方があまり出回っていないようなので備忘録を兼ねてメモしておきます。
###コントロールの配置
ここはVlc.DotNet.Wpfとおなじ。

```xml:MainWindow.xaml
<Window x:Class="LibVLCSharpTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:LibVLCSharpTest"
        xmlns:vlc="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
    <Grid>
        <vlc:VideoView x:Name="player"/>
    </Grid>
</Window>
```

###初期化
まず初めにLibVLCSharp.Shared.Core.Initialize()メソッドを呼びます。
サンプルコードもそんな感じでした。
不思議ポイント、前述の開発環境ではCore.Initialize()を呼ばなくても何故か動きます。
本番環境では呼ばないとダメでした。
呼んでおくのが無難です。というか何故呼ばずに動く。一番最初に呼んでねってどこかに書いてありました。

```csharp:MainWindow.xaml.cs
using LibVLCSharp.Shared;
using LibVLCSharp.WPF;
namespace LibVLCSharpTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Core.Initialize(); //これがなくても動く環境がある。
        }
    }
}
```

###ライブラリパス設定
これはnew LibVLC()するだけで済むのでLibVLCSharpの方が簡単です。
起動時のオプション引数はここでコンストラクタに食わせましょう。

```csharp:LibVLCSharp
player.MediaPlayer = new MediaPlayer(new LibVLC());
```

###メディア設定
実行ファイルと同じディレクトリにある"sample.mp4"を再生する例です。
LibVLCSharp.Shared.Mediaクラスのインスタンスで再生するメディアを指定します。
ここでもなぜかLibVLCSharp.Shared.LibVLCが要求されます。

```csharp:LibVLCSharp
string FileName = "sample.mp4";
FileInfo MediaSource1 = new FileInfo(System.IO.Path.Combine(new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).DirectoryName, FileName));
Media Media1 = new Media(new LibVLC(), MediaSource1.FullName);
```

###再生・停止
先ほど作ったMediaクラスのインスタンスを引数に与えたり与えなかったりして再生します。
この辺はVlc.DotNet.Wpfと似たような感じ。

```csharp:LibVLCSharp
player.MediaPlayer.Play(Media1);
player.MediaPlayer.Stop();
```

###音量調整・ミュート
Muteにするメソッドはなかったので、音量0を指定することでミュートします。enumとか宣言してもいいかもね。
音量は0～100のパーセンテージで設定してやります。

```csharp:LibVLCSharp
player.MediaPlayer.Volume = 0;
```

###気をつけた方がよさそうなこと
どこかに書いてあった気がしますが、スレッドセーフではないので気を付けましょう。
LibVLCのインスタンスはひとつだけの方がいいらしい。
Dispose()した方がいいらしい。（Vlc.DotNet.Wpfだとガベージコレクタ任せにしないとフリーズするって話だったが…）
####ループ再生にはご用心
ループ再生が一筋縄ではいきません。
コマンドラインからVLC Media Playerを使うときは--loopを指定してやればいいのですが、

```csharp
player.MediaPlayer = new MediaPlayer(new LibVLC("--loop"));
```

とやってもループ再生してくれません。
[同じことに悩んだ人が居たようです](https://stackoverflow.com/questions/56487740/how-to-achieve-looping-playback-with-libvlcsharp)が、有限回のループ回数を指定してごまかすしかないようです。

```csharp
player.MediaPlayer = new MediaPlayer(new LibVLC("--input-repeat=65535"));
```

はよ直してくれ…
#感想
Vlc.DotNet.Wpfさん、Task.Run()とかで別スレッドで走らせてやらないとすぐ固まったり、スレッドセーフじゃなかったりとなんとも不穏というか自分と相性が合わない（？）ような感じでしたが、環境の差を吸収できずに動かないとなかなか困らせてくれました。
そもそもOSとか.Netとかって環境の差が出ないようにするためのやつじゃなかったんですかい…？とか思わなくもないんですがね…
LibVLC自体が同じWindows10でも微妙に動作が違っているっぽいのがLibVLCSharpでわかった（？）のでVlc.DotNet.Wpfさんが悪いというわけではなさそうですが、プログラム書くのが本職ではない私にはつらかったなあ…

**なんだかよくわからないけど、とりあえず動いてるからﾖｼｯ**