[WPF]映画の同時視聴ができるアプリを作ってみた

#はじめに
初めて記事を投稿するNKです。
一か月ほど前からWPFでアプリを作り始めたため、日記として作っていきたいと思います。
プログラミング経験としては1年弱なので、見にくいコードを掲示することがありますのであらかじめご了承ください。

#これから作るもの
最近、配信者が映画を視聴者と一緒に同時視聴するということが増えてきており、その際に便利なアプリケーションが欲しいと思っておりました。いつもはtwitchのWatchPartyで同時視聴しているのですが、アーカイブで見る際はWatchParty機能が使えず、自分でウィンドウを2つ並べて見るしかありません。

私自身、基本リアルタイムで配信を見るということが少なく、アーカイブで見ることが多いため、上記の方法を使って同時視聴を行っていましたが、それだとウィンドウのサイズを変えることも一苦労であるため、非常に不便です。そのため、もっと気楽に見れるアプリがあればと思い作ってみました。

#画面設計
1つはAmazon video,Huluのような映画の画面。 
もう一つはyoutubeやTwitchなど同時視聴をしてくれる配信者を映す画面。

この二つを一つのウィンドウにくっつけた形となります。
↓のように二つWebViewを使います。
![スクリーンショット 2021-10-01 224746.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/c607781c-d36a-c056-53d6-703b0d1fd41f.png)

またTwitchやYoutubeにすぐ切り替えることができる便利なUIも追加します。

#環境
VisualStudio 2019
.NETCORE 3.1


#参考サイト一覧
https://noitalog.tokyo/wpf-material-design/

https://marunaka-blog.com/webbrowsertopmost-01/144/

https://docs.microsoft.com/ja-jp/microsoft-edge/webview2/get-started/wpf

#制作日記
###1H.264コーデックに対応しているブラウザを探す。
###2WebView2を使ってウィンドウに二つブラウザを埋め込む
###3簡単なUIを作る

#1H.264コーデックに対応しているブラウザを探す。

正直この工程が最も時間がかかりました。
WPFで使用できる埋め込み式ブラウザは結構種類があったのですが、中々H264というビデオコーデックに対応しているブラウザがなく、行き詰りました。

###試したこと
Chromiumをアプリケーションに組み込むことができるCefSharpという埋め込みブラウザを試しました。
しかし、残念ながら、H264コーデックに対応しておりませんでした。

他にもEO.WebBrowser(Google Chrome) 、Gecko(Firefox)を試しましたがすべてH264コーデックに対応しておらず、Twitch,Amazon videoを再生することができませんでした。

日本語、英語を使って情報収集したところ
Microsoft Edgeを使用しているWebView2がH264コーデックに対応しているとのことだったので、最終的にはこれを使用しました。


#2WebView2を使ってウィンドウに二つブラウザを埋め込む
まずいつも通りVisual StudioでWPFのテンプレートを作りました。

また、Webview2を実際にWPFで使うために準備することとして、

1.NugetのライブラリでMicrosoft Edge WebView2をインストール。
![スクリーンショット 2021-10-02 111147.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/45e4efb0-bf98-5666-7cfe-424daa474fd9.png)

2.https://developer.microsoft.com/en-us/microsoft-edge/webview2/　
↑のURLでWebview2を起動させるためのRuntimeをインストール。

以上の二つの準備を行いwebview2を使用可能にしました。
(runtimeをインストールしなければそもそも起動できないことが後に発覚)

XAMLを使ってWebView2を画面に埋め込み、最初の画面に出したい URL(今回はGoogle)をSourceに書き込む。

もう一つの画面を追加する際、被って見えなくなるということがあったのでしっかりとGridSplitterで分割し、二つ目のWebView2を埋め込む。

デザイナーイメージ↓
![デザイナー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/519fe7fa-fad3-abbc-24cf-251814a6314a.png)

XAMLコード

```
<Window
    x:Class="Test.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:Test"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="10" />
            <ColumnDefinition />
            <ColumnDefinition Width="0*" />

        </Grid.ColumnDefinitions>

        <GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
        <Grid
            Grid.Column="0"
            Grid.ColumnSpan="2"
            Margin="-2,0,4,0">
            <Wpf:WebView2 Margin="0,0,3,0" Source="https://www.google.com/" />

        </Grid>
        <Grid
            Grid.Column="1"
            Grid.ColumnSpan="2"
            Margin="4,0,0,0">
            <wv2:WebView2 Margin="10,0,0,0" Source="https://www.google.com/" />
        </Grid>

    </Grid>
</Window>

```

忘れそうになるが

``` 
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf""

```
を忘れずに追加すること。

出来上がった画面がこちら↓
![スクリーンショット 2021-10-02 141059.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/f30a6088-6418-8781-b7ea-8384cc2d7460.png)


amazon prime videoの規約でスクリーンショットをすることはどうやら禁じられているようなので、写真をここにアップロードすることはかないませんでしたが、amazon prime videoでの視聴、twitchでの視聴ともに可能でした!


しかし、このままでは前に戻る　後ろに戻るなどがなく、機能的に不便であったため、次はUIを追加しようと思います。

#3簡単なUIを作る
少しモダンな感じで作りたかったため、Material Design In XAML Toolkitを使ってボタン、ロゴなどの素材を使ってUI作り。

主に作るUIとしては
1. ブラウザの後ろに戻る、前に進む機能の追加。
2. Google,Youtube,Twitchなどよく使うフォームにすぐ移動できる機能。
3. ウィンドウを前面配置切り替えをすることができる機能。 

の3つ。

まずUIを配置できるようGridSplitterで分割し全部で4画面に分けました。2つはwebview2でのち2つはUI用。

###1.ブラウザの後ろに戻る、前に進む機能の追加
![スクリーンショット 2021-10-02 150000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/0700df8b-24ff-eb61-8737-cec8d716cae2.png)
まずはこのようにボタンを貼り付け、Clickを実行した時の設定をMainWindow.xaml.csで行います。


        private void Back_Click(object sender, RoutedEventArgs e)
        {
           webView.GoBack();
        }

        private void Forward_Click(object sender, RoutedEventArgs e)
        {
            webView.GoForward();
        }

Backが後ろに戻る用　Forwardが前に進む用で、これをもう一つのwebviewにも同じことを行います。

これで1は完了です。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/43f4e7f6-f092-f3be-1105-2ec6e4a62b20.gif)


###2 Google,Youtube,Twitchなどよく使うフォームにすぐ移動できる機能

こちらの機能も同じくボタンを作り、それをクリックすると指定したURLに飛ばすというコードを書きます。

2ではこちらのサイトを参考にさせていただきました。
https://docs.microsoft.com/ja-jp/microsoft-edge/webview2/get-started/wpf


![スクリーンショット 2021-10-02 150630.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/33a062c2-f25f-adea-0197-eeebe5bc9f1f.png)

書いたコード

        private void HomeButton_Clickw2(object sender, RoutedEventArgs e)
        {
            webView2.CoreWebView2.Navigate("https://www.google.com/");
        }

        private void twitch_Clickw2(object sender, RoutedEventArgs e)
        {
            webView2.CoreWebView2.Navigate("https://www.twitch.tv/");
        }

        private void youtube_Clickw2(object sender, RoutedEventArgs e)
        {
            webView2.CoreWebView2.Navigate("https://www.youtube.com/"); 
        }


WebView2(指定したwebviewの名前).CoreWebView2.Navigate("飛ばしたいURL")
といった感じです。

![Animation2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/a387e537-68f3-5b26-32d6-00806b490add.gif)

###3 ウィンドウを前面配置切り替えをすることができる機能。

まずはボタンを作成し、先ほどのようにイベントを設定します。

ウィンドウのTopmostを活用し、Topmostがfalseのときボタンを押された場合topmostをtrueに変え、色も少し赤みがかかった色に変更。

切り替えの機能にしたいため、Topmostがtrueでボタンが押された際は、Topmostをfalseにして色を戻すというコードを書きました。

多分コード見た方が説明するより早そうですね

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if(Topmost == true)
            {
                Topmost = false;
                Pin.Background = new SolidColorBrush(Colors.BlueViolet);
            }
            else
            {
                Topmost = true;
                Pin.Background = new SolidColorBrush(Colors.PaleVioletRed);
            }
        }

![Animation3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/27fd0520-4b53-f360-20ad-1b0c11ef646a.gif)

今回のアプリXaml コードすべて
(URL検索機能含む)

``` 
 <Window
    x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
    xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
    Title="WatchAlongParty"
    Width="900"
    Height="600"
    mc:Ignorable="d">
    <Window.Resources>
        <Color x:Key="SolidColorBrushColor1">#FF673AB7</Color>
    </Window.Resources>

    <Grid x:Name="Main">

        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="5" />
            <ColumnDefinition />
            <ColumnDefinition Width="0*" />

        </Grid.ColumnDefinitions>
        <GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
        <GridSplitter
            Grid.Column="1"
            Grid.ColumnSpan="2"
            Margin="4,0,0,0"
            HorizontalAlignment="Stretch" />


        <Grid
            Grid.Column="0"
            Grid.ColumnSpan="2"
            Margin="-2,0,4,0">
            <Grid.RowDefinitions>

                <RowDefinition Height="60*" />
                <RowDefinition Height="5" />
                <RowDefinition Height="150*" />
            </Grid.RowDefinitions>
            <GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" />
            <local:UserControl1
                Height="58"
                Margin="0,0,1,0"
                VerticalAlignment="Top" />
            <wv2:WebView2
                Name="webView"
                Grid.Row="2"
                Margin="-1,0,1,0"
                Source="https://www.primevideo.com/" />
            <Button
                x:Name="Forward"
                Width="63"
                Margin="0,63,84,50"
                HorizontalAlignment="Right"
                Click="Forward_Click"
                Cursor="Hand">
                <DockPanel>
                    <materialDesign:PackIcon
                        Width="28"
                        Height="20"
                        Kind="ArrowRight" />
                </DockPanel>
            </Button>
            <Button
                x:Name="HomeButton"
                Width="58"
                Margin="0,63,0,50"
                HorizontalAlignment="Center"
                Background="#FF5A35DA"
                BorderBrush="White"
                Click="HomeButton_Click"
                Cursor="Hand">
                <DockPanel>
                    <materialDesign:PackIcon
                        Width="26"
                        Height="20"
                        Kind="Google" />
                </DockPanel>
            </Button>
            <Button
                x:Name="Back"
                Width="63"
                Margin="75,63,0,50"
                HorizontalAlignment="Left"
                Click="Back_Click"
                Cursor="Hand"
                FontSize="11">
                <DockPanel>
                    <materialDesign:PackIcon
                        Width="29"
                        Height="20"
                        Kind="ArrowLeft" />
                </DockPanel>
            </Button>
            <TextBox
                x:Name="Url1"
                Height="25"
                Margin="11,0,67,14"
                VerticalAlignment="Bottom"
                Cursor="IBeam"
                Text=""
                TextWrapping="Wrap" />
            <Button
                x:Name="SearchUrl1"
                Width="52"
                Height="34"
                Margin="0,0,10,14"
                HorizontalAlignment="Right"
                VerticalAlignment="Bottom"
                Click="SearchUrl1_Click"
                Cursor="Hand">
                <materialDesign:PackIcon
                    Width="21"
                    Height="19"
                    Kind="Magnify" />
            </Button>




        </Grid>
        <Grid
            Grid.Column="1"
            Grid.ColumnSpan="2"
            Margin="4,0,0,0">
            <wv2:WebView2
                Name="webView2"
                Grid.Row="2"
                IsEnabled="True"
                Source="https://www.twitch.tv/" />
            <GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" />
            <Button
                x:Name="IcnPin"
                Width="65"
                Margin="0,10,0,49"
                HorizontalAlignment="Center"
                Background="#FF673AB7"
                Click="Button_Click"
                Cursor="Hand">
                <DockPanel>
                    <materialDesign:PackIcon Kind="Pin" />
                </DockPanel>
            </Button>
            <Button
                x:Name="Backweb2"
                Width="64"
                Margin="73,42,0,23"
                HorizontalAlignment="Left"
                Click="Back_Clickw2"
                Cursor="Hand">
                <DockPanel>
                    <materialDesign:PackIcon
                        Width="29"
                        Height="21"
                        Kind="ArrowLeft" />
                </DockPanel>
            </Button>
            <Button
                x:Name="Forwardweb2"
                Width="72"
                Margin="0,42,68,23"
                HorizontalAlignment="Right"
                Click="Forward_Clickw2"
                Cursor="Hand">
                <DockPanel>
                    <materialDesign:PackIcon
                        Width="37"
                        Height="23"
                        Kind="ArrowRight" />
                </DockPanel>
            </Button>
            <Button
                x:Name="HomeButtonweb2"
                Grid.RowSpan="2"
                Width="92"
                Margin="0,69,0,1"
                HorizontalAlignment="Center"
                Background="#FF5A35DA"
                BorderBrush="White"
                Click="HomeButton_Clickw2"
                Cursor="Hand">
                <DockPanel>
                    <materialDesign:PackIcon Kind="Google" />
                </DockPanel>
            </Button>
            <Button
                x:Name="ttwitchweb2_Copy"
                Width="62"
                Height="32"
                Margin="0,3,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Click="twitch_Clickw2"
                Cursor="Hand">
                <DockPanel>
                    <materialDesign:PackIcon
                        Width="20"
                        Height="19"
                        Kind="Twitch" />
                </DockPanel>
            </Button>
            <Button
                x:Name="youtubeweb2_Copy1"
                Width="62"
                Height="32"
                Margin="73,3,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Background="#FFB73A3A"
                Click="youtube_Clickw2"
                Cursor="Hand">
                <DockPanel>
                    <materialDesign:PackIcon
                        Width="20"
                        Height="19"
                        Kind="Youtube" />
                </DockPanel>
            </Button>
            <TextBox
                x:Name="Url2"
                Height="25"
                Margin="6,0,71,14"
                VerticalAlignment="Bottom"
                Cursor="IBeam"
                Text=""
                TextWrapping="Wrap" />
            <Button
                x:Name="SearchUrl2"
                Width="52"
                Height="34"
                Margin="0,0,15,14"
                HorizontalAlignment="Right"
                VerticalAlignment="Bottom"
                Click="SearchUrl2_Click"
                Cursor="Hand">
                <materialDesign:PackIcon
                    Width="21"
                    Height="19"
                    Kind="Magnify" />
            </Button>


            <Grid.RowDefinitions>
                <RowDefinition Height="60*" />
                <RowDefinition Height="5" />
                <RowDefinition Height="150*" />
            </Grid.RowDefinitions>


        </Grid>


    </Grid>

</Window>
``` 

#4まとめ
ということで今回は初めてWPFでアプリづくりを行い、同時視聴アプリを作りました。
制作期間は構想計画合わせて大体2~3週間ほどで、デビュー作としては中々良い出来になりました。
今回初めてWindowsアプリケーションに取り組みましたが、感想としてはxamlが思ったよりも難しいということです。
ただ、ツールボックスからボタンを貼るだと思っていましたが、そんなに甘くないですね...

今までUnity C#を使ってゲームを作ってきたので、C#を書くのは難しくなかったですが、(今回は書く量も少なかったですし...)
xamlは初めてであったためかなり苦戦を強いられました。特にGridSplitterを使って画面分割をするには、Grid自体の理解やxamlのコードの構成をしっかりと理解しなければならなかったため、苦労しました。
WPFは情報が少ないため日本語以外の言語を併用しつついろいろと情報収集を重ねることで勉強しました。

このアプリケーションはアジャイル形式でどんどん新しい機能を後に追加したいと考えているので、これから自分でこのアプリを実際に自分でテストし、不便である部分を改善していきたいと思います。

最後にもう一つだけ。
TwitterなどのSNSでアプリをgithub経由で配布してユーザーに使ってもらった結果、URLで検索できる機能が欲しいとのことであったため、URLで検索できる機能を追加しました。

追加と言っても、先ほど参考にさせていただいたサイトのモノを流用させていただいたまでですが、一応書いたコードだけ残しておきます。
https://docs.microsoft.com/ja-jp/microsoft-edge/webview2/get-started/wpf

Textboxとボタンを置いて、Textboxに入力されたURL文をNavigateに返すといった感じでしょうか。

``` 
private void SearchUrl1_Click(object sender, RoutedEventArgs e)
        {
            if(webView != null && webView.CoreWebView2 != null && Url1.Text != "")
            {
                webView.CoreWebView2.Navigate(Url1.Text);
            }
        }
``` 

#URL機能の問題点-修正済み
URL機能を作ってみましたが、これには少し問題があって現在試行錯誤中です。
問題としてはTextboxにURL以外の文(例えば文字、単語など)を入力し検索してしまうとエラーが発生し、フリーズしてしまうという問題点です。一応応急処置としてTextboxが空の状態でクリックしても何も起こらないようプログラミングしましたが、URL文を誤って入力し、ボタンを押してしまった場合いちいちフリーズしてしまうといった問題は解決されていないため、早急に修正する必要があります。

#URL機能の問題点修正
Try Catchを使って　エラーメッセージをキャッチしてエラーメッセージを表示させる機能を作ることで、フリーズしてしまうバグを修正しました。

![Animation34.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905985/1907ce36-cfc4-875e-5d74-b23aa4253e0b.gif)

コード

``` 

private void SearchUrl1_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (webView != null && webView.CoreWebView2 != null && Url1.Text != "")
                {
                    webView.CoreWebView2.Navigate(Url1.Text);
                }
            }

            catch(System.ArgumentException)
            {
                ErrorMessage Em = new ErrorMessage();
                Em.Show();
            }
        
        }

``` 



#最後に
これからもこのアプリはエラーを直すだけではなく、どんどん機能を追加しより便利な物にしていきたいと考えています。自分自身どんなアプリになるか非常に楽しみです。

初めての記事で読みづらい部分もありましたが、ここまでお読みいただきありがとうございました。

実際にできたアプリ


https://github.com/Rabb1tStd/Watchapp