[Xamarin.Forms]VLCライブラリを使ってiOSでWebMを再生する方法

## やりたいこと
サーバ側でWebM形式で保存されている動画をダウンロードしてAndroidとiOSのアプリで再生したい。
Androidは標準のVideoViewでWebM形式の動画を再生することができるが、
iOSの動画プレーヤーはWebM形式には対応していないため、WebMを再生できるライブラリが必要...
再生中はシークバーと経過時間などを表示したい。

## 使用したライブラリ
多くのコーデックに対応しているVLCが提供しているライブラリ[LibVLCSharp](https://github.com/videolan/libvlcsharp)を使えば、iOSでもWebMを再生できそう。
ただ、ライブラリを見てみるとVideoViewはあくまで動画再生部分のコントロールであって、シークバーなどは用意されていないため、自分で実装する必要がある。

## 使用するにあたって
* LibVLCSharpはPCLには対応していないので.NETStandardにしなければならない。
* Xamarin.Formsのバージョンを3.2.0以上にする。

## 実装方法(.NET Standardのライブラリ側)
1. NuGetで「LibVLCSharp.Forms(0.5.0)」を追加。
2. MainPage.xaml.cs(動画プレーヤーページ)のOnAppearingメソッドで初期化処理を記述。
xaml側のソースは割愛します。詳しくは下記のGitHubを参照してください。

    ```cs:MainPage.xaml.cs
    protected override void OnAppearing()
    {
        base.OnAppearing();
        
        // VLC Initialize
        Core.Initialize();
    
        var libVLC = new LibVLC();
        _media = new Media(libVLC, "http://www.quirksmode.org/html5/videos/big_buck_bunny.webm", FromType.FromLocation);
        
        VideoView.MediaPlayer = new MediaPlayer(libVLC) { Media = _media };
        VideoView.MediaPlayer.TimeChanged += OnMediaPlayerTimeChanged;
        VideoView.MediaPlayer.LengthChanged += OnMediaPlayerLengthChanged;
        VideoView.MediaPlayer.EndReached += OnMediaPlayerEndReached;
        PlaybackSlider.TouchUp += OnPlaybackSliderTouchUp;
        ...
    }
    ```

3. 再生・一時停止ボタン押下処理

    ```cs:MainPage.xaml.cs
    private void OnPlayImageButtonClicked(object sender, EventArgs e)
    {
        if (VideoView.MediaPlayer.State == VLCState.Playing)
        {
            // Pause
            VideoView.MediaPlayer.Pause();
            PlayImageButton.Source = _playImage;
        }
        else
        {
            if (VideoView.MediaPlayer.State == VLCState.Ended)
            {
                VideoView.MediaPlayer.Media = _media;
            }

            // Play
            VideoView.MediaPlayer.Play();
            PlayImageButton.Source = _pauseImage;
            Tool.IsVisible = true;
        }
    }
    ```

4. LengthChangedイベントで動画の長さを保持

    ```cs:MainPage.xaml.cs
    private void OnMediaPlayerLengthChanged(object sender, MediaPlayerLengthChangedEventArgs e)
    {
        _length = e.Length;
    }
    ```
5. TimeChangedイベントで動画再生時の時間処理を記述

    ```cs:MainPage.xaml.cs
    private void OnMediaPlayerTimeChanged(object sender, MediaPlayerTimeChangedEventArgs e)
    {
        var elapsedTimeSpan = TimeSpan.FromMilliseconds((double)e.Time);                // ElapsedTime
        var remainingTimeSpan = TimeSpan.FromMilliseconds((double)(_length - e.Time));  // RemainingTime
        var elapsedTotalSeconds = Math.Floor(elapsedTimeSpan.TotalSeconds);             // ElapsedTime(TotalSeconds)

        // Draw
        Device.BeginInvokeOnMainThread(() => {
            PlaybackSlider.Value = VideoView.MediaPlayer.Position;
        });

        if (Math.Abs(_elapsedTotalSeconds - elapsedTotalSeconds) < 1)
        {
            return;
        }

        // Backup TotalSeconds
        _elapsedTotalSeconds = elapsedTotalSeconds;

        // Draw(1Seconds)
        Device.BeginInvokeOnMainThread(() => {
            ElapsedTime.Text = string.Format("{0:D2}:{1:D02}", elapsedTimeSpan.Minutes, elapsedTimeSpan.Seconds);
            RemainingTime.Text = string.Format("{0:D2}:{1:D02}", remainingTimeSpan.Minutes, remainingTimeSpan.Seconds);
        });
    }
    ```

6. EndReachedイベントで動画終了処理を記述

    ```cs:MainPage.xaml.cs
    private void OnMediaPlayerEndReached(object sender, EventArgs e)
    {
        _elapsedTotalSeconds = 0;
    
        // Draw
        Device.BeginInvokeOnMainThread(() => {
            PlaybackSlider.Value = 0;
            PlayImageButton.Source = _playImage;
            Tool.IsVisible = false;
            ElapsedTime.Text = "00:00";
            RemainingTime.Text = "00:00";
        });
    }
    ```

7. シークバーのタップイベントで動画のポジションを変更
シークバーの詳しい実装については下記のGitHubを参照してください。

    ```cs:MainPage.xaml.cs
    private void OnPlaybackSliderTouchUp(object sender, EventArgs e)
    {
        // Draw
        Device.BeginInvokeOnMainThread(() => {
            VideoView.MediaPlayer.Position = (float)sender;
        });
    }
    ```

## 実装方法（iOS側）
1. NuGetで「VideoLAN.LibVLC.iOS(3.1.5-alpha)」を追加。
2. シークバーのRendererを実装。(GitHubを参照)

## 実装方法（Android側）
1. NuGetで「VideoLAN.LibVLC.Android(3.0.0)」を追加。
2. シークバーのRendererを実装。(GitHubを参照)

## 注意点
Androidで動画ページから別ページに遷移したり、アプリをバックグラウンドに移行すると、
「[Adreno] DequeueBuffer: dequeueBuffer failed」のエラーが出続けてしまうので、
ページのOnDisappearingでVideoView.MediaPlayerを明示的にDisposeする必要がある。
App.xaml.csのOnSleepでもDisposeする。

## 実行
さすがVLCだけあって綺麗に動画が再生されました。
### iOS
<img width="375" alt="iOS" src="https://qiita-image-store.s3.amazonaws.com/0/143861/de4f6692-7f92-3454-e94d-39ca7851e128.png">

### Android
<img width="375" alt="Android" src="https://qiita-image-store.s3.amazonaws.com/0/143861/ae477f8a-018c-f2a1-5f90-d2d2a71da1c7.png">

## ソース
https://github.com/unbam/XamarinVLCSample

## 今後
動画の回転表示や、再生ボタンをオーバレイ化したい。