Maui Blazor Camera Implementation

Maui Blazor Camera Implementation

Since the interface in Maui Blazor is rendered by WebView, it cannot access the Android camera directly because the native camera requires binding to UI components.

Last updated 1/12/2023 8:51 PM
dotnet-simple
6 min read
Category
MAUI Blazor
Tags
.NET C# Blazor MAUI

This article was contributed by a netizen.

Author: dotnet-simple

Original title: Maui Blazor Using Camera Implementation

Original link: https://www.cnblogs.com/hejiale010426/p/17045707.html

Since the UI in Maui Blazor is rendered by WebView, it is not possible to access the Android camera directly because the native camera requires binding to a UI component. I found an alternative approach: using js within WebView to call the device camera, which supports multi-platform compatibility. I have tested it on Android and PC. Since I don't have iOS or macOS devices, I couldn't test them, but it is likely compatible; dynamic permission requests may be needed.

1. Add JS Methods

Create a helper.js file in the wwwroot folder and add the following two JavaScript functions.

Add <script src="helper.js"></script> in index.html to include the JS.

/**
 * Set the src of an element
 * @param {any} canvasId The DOM id of the canvas
 * @param {any} videoId The DOM id of the video element
 * @param {any} srcId The DOM id of the img element
 * @param {any} width Set the screenshot width
 * @param {any} height Set the screenshot height
 */
function setImgSrc(dest, videoId, srcId, width, height) {
  let video = document.getElementById(videoId);
  let canvas = document.getElementById(canvasId);
  console.log(video.clientHeight, video.clientWidth);

  canvas.getContext("2d").drawImage(video, 0, 0, width, height);

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

/**
 * Initialize the camera
 * @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)";
      });
  }
}

Then, achieve compatibility across platforms.

Android:

Platforms/Android/AndroidManifest.xml file content

<?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" />
	<!--Camera permission-->
	<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" />
	<!--Camera feature-->
	<uses-feature android:name="android.hardware.camera" />
	<!--Audio recording permission-->
	<uses-permission android:name="android.permission.RECORD_AUDIO" />
	<!--Location permission-->
	<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 file content

[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);
        // Request required permissions; can also be requested at runtime
        ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.Camera, Manifest.Permission.RecordAudio, Manifest.Permission.ModifyAudioSettings }, 0);
    }
}

MauiWebChromeClient.cs file content

#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

Add the following code in MauiProgram.cs; without it, the camera permission may not be granted (see this issue):

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

The above is the Android adaptation code. For PC, no additional configuration is required. iOS and macOS are unclear.

Then, write the UI:

@page "/" @*Route*@

@inject IJSRuntime JSRuntime @*Inject jsRuntime*@

@if(OpenCameraStatus) @*Hide video when camera is not opened*@
{
    <video id="@VideoId" width="@width" height="@height" />
    <canvas class="d-none" id="@CanvasId" width="@width" height="@height" />
}
<button @onclick="" style="margin:8px">Open Camera</button>
@*Camera must be triggered manually; if the page is navigated by sliding, the method cannot be called directly, so a button is added*@
<button style="margin:8px">Screenshot</button> @*Capture an image from the video stream*@

<img id="@ImgId" />

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

    private async Task OpenCamera()
    {
        if (!OpenCameraStatus)
        {
            // Camera must be triggered by user action; sliding to the page will not trigger it
            await JSRuntime.InvokeVoidAsync("startVideo", "videoFeed");
            OpenCameraStatus = true;
            StateHasChanged();
        }
    }

    protected override async Task OnInitializedAsync()
    {
        // Initialize ids
        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, width, height);
    }
}

Now you can run the program and see the result.

Sample code:

Shared by token

Tech exchange group: 737776595

Keep Exploring

Related Reading

More Articles
Same category / Same tag 4/26/2022

Using Masa Blazor in MAUI

Using `.NET MAUI`, you can develop applications that run on `Android`, `iOS`, `macOS`, and `Windows`, Linux (community-supported) from a single shared codebase. One codebase runs on multiple platforms.

Continue Reading