敗北から勝利までのメモ
何がしたかった
宣伝
C#とBlazorでホロライブファン向けの動画ビューワー『ホロビューワー』を開発しました
結論
Windowsでは WebView2 CoreクラスのCapturePreviewメソッドを使用する。
以下PNGフォーマットで保存する場合のコード
- Sharedコード (UI)
<ContentView>
<StackLayout>
// 省略
<StackLayout Orientation="StackOrientation.Horizontal">
<Button Text="Capture" OnClick="CaptureSingle" />
</StackLayout>
<Grid HorizontalOptions="LayoutOptions.FillAndExpand" VerticalOptions="LayoutOptions.FillAndExpand">
<StackLayout>
<BlazorWebView @ref="BlazorWebViews" VerticalOptions="LayoutOptions.FillAndExpand">
<HoloViewer.WebUI.App />
</BlazorWebView>
</StackLayout>
</Grid>
// 省略
</StackLayout>
</ContentView>
@code
{
private List<BlazorWebView> blazorWebViews = new List<BlazorWebView>();
public BlazorWebView BlazorWebViews { set { blazorWebViews.Add(value); } }
void CaptureSingle ()
{
DependencyService.Get<IScreenCapture>().CaptureSingle(blazorWebViews.First());
}
}
- Sharedコード (DependencyService定義)
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.MobileBlazorBindings.Elements;
namespace HoloViewer
{
public interface IScreenCapture
{
protected const string CaptureFileNameFormat = "yyyyMMdd_HHmmss";
protected const string CaptureFileExtension = ".png";
void CaptureSingle (BlazorWebView blazorWebView);
}
}
- Windows WPF側のコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using Microsoft.MobileBlazorBindings.Elements;
using Xamarin.Forms.Platform.WPF;
[assembly: Xamarin.Forms.Dependency(typeof(HoloViewer.Windows.ScreenCapture))]
namespace HoloViewer.Windows
{
class ScreenCapture : IScreenCapture
{
private async void Capture (BlazorWebView blazorWebView)
{
using (var fileStream = new FileStream(DateTime.Now.ToString(IScreenCapture.CaptureFileNameFormat) + IScreenCapture.CaptureFileExtension, FileMode.Create))
{
await WebView.CastWebView(blazorWebView).CoreWebView2.CapturePreviewAsync(Microsoft.Web.WebView2.Core.CoreWebView2CapturePreviewImageFormat.Png, fileStream);
}
}
public void CaptureSingle (BlazorWebView blazorWebView)
{
Capture(blazorWebView);
}
}
}
- BlazorWebViewからWebView2への変換処理のコード
- WebView2本体はアクセス範囲がpublicじゃないのでリフレクションでアクセスする。
- この辺はデバッガで変数の中身をしらみつぶしにして探した。
using System.Reflection;
using Microsoft.MobileBlazorBindings.Elements;
using Microsoft.MobileBlazorBindings.WebView.Elements;
using Microsoft.Web.WebView2.Wpf;
namespace HoloViewer.Windows
{
class WebView
{
public static WebView2 CastWebView (BlazorWebView blazorWebView)
{
var content = ((Microsoft.MobileBlazorBindings.WebView.Elements.MobileBlazorBindingsBlazorWebView)blazorWebView.NativeControl).Content;
var type = content.GetType();
return (WebView2)type.GetProperty("RetainedNativeControl").GetValue(content);
}
}
}
勝利に至るまでの経過
RenderTargetBitmapを使用するパターン
private void Capture ()
{
var mainWindow = Application.Current.MainWindow;
var renderTargetBitmap = new RenderTargetBitmap((int)mainWindow.Width, (int)mainWindow.Height, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(mainWindow);
var pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (var fileStream = new FileStream(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忘れパターン
private void Capture (BlazorWebView blazorWebView)
{
using (var fileStream = new FileStream(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版は別の地獄がまっているはず