Maui Blazor 使用攝像頭實現

2023-01-12 12:00:26

Maui Blazor 使用攝像頭實現

由於Maui Blazor中介面是由WebView渲染,所以再使用Android的攝像頭時無法去獲取,因為原生的攝像頭需要繫結介面元件
所以我找到了其他的實現方式,通過WebView使用js呼叫裝置攝像頭 支援多平臺相容目前測試了Android 和PC 由於沒有ioc和mac無法測試 大概率是相容可能需要動態申請許可權

1. 新增js方法

我們再wwwroot中建立一個helper.js的檔案並且新增以下倆個js函數
index.html中新增<script src="helper.js"></script>引入js


/**
 * 設定元素的src
 * @param {any} canvasId canvasId的dom id
 * @param {any} videoId video的dom id
 * @param {any} srcId img的dom id
 * @param {any} widht 設定截圖寬度
 * @param {any} height 設定截圖高度
 */
function setImgSrc(dest,videoId, srcId, widht, height) {
    let video = document.getElementById(videoId);
    let canvas = document.getElementById(canvasId);
    console.log(video.clientHeight, video.clientWidth);
   
    canvas.getContext('2d').drawImage(video, 0, 0, widht, height);

    canvas.toBlob(function (blob) {
        var src = document.getElementById(srcId);
        src.setAttribute("height", height)
        src.setAttribute("width", widht);
        src.setAttribute("src", URL.createObjectURL(blob))
    }, "image/jpeg", 0.95);
}


/**
 * 初始化攝像頭
 * @param {any} src 
 */
function startVideo(src) {
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({ video: true }).then(function (stream) {
            let video = document.getElementById(src);
            if ("srcObject" in video) {
                video.srcObject = stream;
            } else {
                video.src = window.URL.createObjectURL(stream);
            }
            video.onloadedmetadata = function (e) {
                video.play();
            };
            //mirror image
            video.style.webkitTransform = "scaleX(-1)";
            video.style.transform = "scaleX(-1)";
        });
    }
}

然後各個平臺的相容

android:

Platforms/Android/AndroidManifest.xml檔案內容

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
	<application android:allowBackup="true" android:supportsRtl="true"></application>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.INTERNET" />
	<!--相機許可權-->
	<uses-permission android:name="android.permission.CAMERA" android:required="false"/>
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
	<!--相機功能-->
	<uses-feature android:name="android.hardware.camera" />
	<!--音訊錄製許可權-->
	<uses-permission android:name="android.permission.RECORD_AUDIO" />
	<!--位置許可權-->
	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
	
	<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
	<uses-feature android:name="android.hardware.location.gps" />

	<queries>
		<intent>
			<action android:name="android.intent.action.VIEW" />
			<data android:scheme="http"/>
		</intent>
		<intent>
			<action android:name="android.intent.action.VIEW" />
			<data android:scheme="https"/>
		</intent>
	</queries>
</manifest>

Platforms/Android/MainActivity.cs檔案內容

[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Platform.Init(this, savedInstanceState);
        // 申請所需許可權 也可以再使用的時候去申請
        ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.Camera, Manifest.Permission.RecordAudio, Manifest.Permission.ModifyAudioSettings }, 0);
    }
}

MauiWebChromeClient.cs檔案內容

#if ANDROID
using Android.Webkit;
using Microsoft.AspNetCore.Components.WebView.Maui;

namespace MainSample;

public class MauiWebChromeClient : WebChromeClient
{
    public override void OnPermissionRequest(PermissionRequest request)
    {
        request.Grant(request.GetResources());
    }
}

public class MauiBlazorWebViewHandler : BlazorWebViewHandler
{
    protected override void ConnectHandler(Android.Webkit.WebView platformView)
    {
        platformView.SetWebChromeClient(new MauiWebChromeClient());
        base.ConnectHandler(platformView);
    }
}

#endif

MauiProgram.cs中新增以下程式碼;如果沒有下面程式碼會出現沒有攝像頭許可權
具體在這個 issue體現


#if ANDROID
        builder.ConfigureMauiHandlers(handlers =>
        {
            handlers.AddHandler<IBlazorWebView, MauiBlazorWebViewHandler>();
        });
#endif

以上是android的適配程式碼 pc不需要設定額外程式碼 ios和mac不清楚

然後編寫介面

@page "/" @*介面路由*@

@inject IJSRuntime JSRuntime @*注入jsRuntime*@

@if(OpenCameraStatus) @*在攝像頭沒有被開啟的情況不顯示video*@
{
    <video id="@VideoId" width="@widht" height="@height" />
    <canvas class="d-none" id="@CanvasId" width="@widht" height="@height" />
}
<button @onclick="" style="margin:8px">開啟攝像頭</button> 
@*因為攝像頭必須是使用者手動觸發如果介面是滑動進入無法直接呼叫方法開啟攝像頭所以新增按鈕觸發*@
<button style="margin:8px">截圖</button> @*對視訊流擷取一張圖*@

<img id="@ImgId" />

@code{
    private string CanvasId;
    private string ImgId;
    private string VideoId;
    private bool OpenCameraStatus;
    private int widht = 320;
    private int height = 500;

    private async Task OpenCamera()
    {
        if (!OpenCameraStatus)
        {
            // 由於開啟攝像頭的條件必須是使用者手動觸發如果滑動切換到介面是無法觸發的
            await JSRuntime.InvokeVoidAsync("startVideo", "videoFeed");
            OpenCameraStatus = true;
            StateHasChanged();
        }
    }

    protected override async Task OnInitializedAsync()
    {
        // 初始化id
        ImgId = Guid.NewGuid().ToString("N");
        CanvasId = Guid.NewGuid().ToString("N");
        VideoId = Guid.NewGuid().ToString("N");
        await base.OnInitializedAsync();
    }

    private async Task Screenshot()
    {
        await JSRuntime.InvokeAsync<String>("setImgSrc", CanvasId,VideoId, ImgId, widht, height);
    }
}

然後可以執行程式就可以看到我們的效果了

範例程式碼:gitee github

來著token的分享

技術交流群:737776595