敗北から勝利までのメモ
何がしたかった
宣伝
C#とBlazorでホロライブファン向けの動画ビューワー『ホロビューワー』を開発しました
結論
Windowsでは WebView2 CoreクラスのCapturePreviewメソッドを使用する。
以下PNGフォーマットで保存する場合のコード
- Sharedコード (UI)
<ContentView><StackLayout>// 省略<StackLayoutOrientation="StackOrientation.Horizontal"><ButtonText="Capture"OnClick="CaptureSingle"/></StackLayout><GridHorizontalOptions="LayoutOptions.FillAndExpand"VerticalOptions="LayoutOptions.FillAndExpand"><StackLayout><BlazorWebView@ref="BlazorWebViews"VerticalOptions="LayoutOptions.FillAndExpand"><HoloViewer.WebUI.App/></BlazorWebView></StackLayout></Grid>// 省略</StackLayout></ContentView>@code{privateList<BlazorWebView>blazorWebViews=newList<BlazorWebView>();publicBlazorWebViewBlazorWebViews{set{blazorWebViews.Add(value);}}voidCaptureSingle(){DependencyService.Get<IScreenCapture>().CaptureSingle(blazorWebViews.First());}}
- Sharedコード (DependencyService定義)
usingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingMicrosoft.MobileBlazorBindings.Elements;namespaceHoloViewer{publicinterfaceIScreenCapture{protectedconststringCaptureFileNameFormat="yyyyMMdd_HHmmss";protectedconststringCaptureFileExtension=".png";voidCaptureSingle(BlazorWebViewblazorWebView);}}
- Windows WPF側のコード
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Media;usingSystem.Windows.Media.Imaging;usingSystem.IO;usingMicrosoft.MobileBlazorBindings.Elements;usingXamarin.Forms.Platform.WPF;[assembly:Xamarin.Forms.Dependency(typeof(HoloViewer.Windows.ScreenCapture))]namespaceHoloViewer.Windows{classScreenCapture:IScreenCapture{privateasyncvoidCapture(BlazorWebViewblazorWebView){using(varfileStream=newFileStream(DateTime.Now.ToString(IScreenCapture.CaptureFileNameFormat)+IScreenCapture.CaptureFileExtension,FileMode.Create)){awaitWebView.CastWebView(blazorWebView).CoreWebView2.CapturePreviewAsync(Microsoft.Web.WebView2.Core.CoreWebView2CapturePreviewImageFormat.Png,fileStream);}}publicvoidCaptureSingle(BlazorWebViewblazorWebView){Capture(blazorWebView);}}}
- BlazorWebViewからWebView2への変換処理のコード
- WebView2本体はアクセス範囲がpublicじゃないのでリフレクションでアクセスする。
- この辺はデバッガで変数の中身をしらみつぶしにして探した。
usingSystem.Reflection;usingMicrosoft.MobileBlazorBindings.Elements;usingMicrosoft.MobileBlazorBindings.WebView.Elements;usingMicrosoft.Web.WebView2.Wpf;namespaceHoloViewer.Windows{classWebView{publicstaticWebView2CastWebView(BlazorWebViewblazorWebView){varcontent=((Microsoft.MobileBlazorBindings.WebView.Elements.MobileBlazorBindingsBlazorWebView)blazorWebView.NativeControl).Content;vartype=content.GetType();return(WebView2)type.GetProperty("RetainedNativeControl").GetValue(content);}}}
勝利に至るまでの経過
RenderTargetBitmapを使用するパターン
privatevoidCapture(){varmainWindow=Application.Current.MainWindow;varrenderTargetBitmap=newRenderTargetBitmap((int)mainWindow.Width,(int)mainWindow.Height,96,96,PixelFormats.Pbgra32);renderTargetBitmap.Render(mainWindow);varpngBitmapEncoder=newPngBitmapEncoder();pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));using(varfileStream=newFileStream(DateTime.Now.ToString(IScreenCapture.CaptureFileNameFormat)+IScreenCapture.CaptureFileExtension,FileMode.Create)){pngBitmapEncoder.Save(fileStream);}}
- 敗北その1の原因 (推測)
- タスクマネージャーでプロセスを見てみると別のプロセスでWebView2が動作している。
- よってMainWindow(画像だと無名になっているプロセス)だけをキャプチャしてもWebViewの箇所が表示されない。 (と推測しています)
WebView2のプロセスが複数ある理由は正直わかっていません。
- 敗北その2
- CapturePreviewAsyncメソッドを await しないと0バイトのPNG画像が生成される。
- そもそもawaitしていなかったり、Wait関数で待っても0バイトのPNG画像が生成されてダメなので注意
await忘れパターン
privatevoidCapture(BlazorWebViewblazorWebView){using(varfileStream=newFileStream(DateTime.Now.ToString(IScreenCapture.CaptureFileNameFormat)+IScreenCapture.CaptureFileExtension,FileMode.Create)){WebView.CastWebView(blazorWebView).CoreWebView2.CapturePreviewAsync(Microsoft.Web.WebView2.Core.CoreWebView2CapturePreviewImageFormat.Png,fileStream);}}
まとめ
- Windows版はこの調子で機能を実装すれば勝てそう
- WebView2がMacには対応していないためMac版は別の地獄がまっているはず