3

I am trying to show live stream from camera & microphone in <video> html element.

In blazor file (sample.razor) I invoke method to request and show video:

protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync("requestMediaAndShow");
        }
    }

In javascript file (sample.js) I request stream and assign to video html element.

// Create request options
let options = {
    audio: true,
    video: true
};

// Request user media
navigator.mediaDevices
    .getUserMedia(options)
    .then(gotLocalStream)
    .catch(logError);

But when I am requesting i catch an error like "NotAllowedError: Permission denied".

AndroidManifest.xml contains:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

MainActivity.cs contains:

public class MainActivity : MauiAppCompatActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.Camera, Manifest.Permission.RecordAudio, Manifest.Permission.ModifyAudioSettings }, 0);
    }
}

Any ideas how to request audio and video stream by javascript in BlazorWebView natively on android? PS. On both on website and natively on Windows platform works great and no extra permissions are required.

Animusz_
  • 71
  • 1
  • 6

4 Answers4

3

Although the Android permissions seem to be granted OK, I suspected the website permissions were not. Not sure if the author of this question is the same person, but an issue was opened on the .NET MAUI repo as well about this.

While we are looking into making this work out of the box, another helpful user has posted this workaround for now.

Implement your own handler like so

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

public class MauiBlazorWebViewHandler : BlazorWebViewHandler
{
    protected override WebChromeClient GetWebChromeClient()
    {
        return new MauiWebChromeClient();
    }
}

And register this handler in your MauiProgram.cs:

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

This will automatically grant all the requested permissions from the web side of things.

Gerald Versluis
  • 30,492
  • 6
  • 73
  • 100
  • Unfortunately this workaround does not seem to work properly, I've updated the github issue with my findings. – DannyBiezen Dec 09 '21 at 21:48
  • Ah maybe I was too quick. I totally looked over your "didn't fix it completely but nice starting point" comment :D – Gerald Versluis Dec 09 '21 at 22:07
  • Turns out I was too quick as well :D, I only looked at the github issue and missed the more detailed changes made in this question. When I add the changes in `MainActivity.cs` everything seems to work great! – DannyBiezen Dec 09 '21 at 22:55
  • @GeraldVersluis in MAUI RC1 the method `GetWebChromeClient()` is no longer available for override on the Android build target. Would you perhaps know what the new path for this might be? Maybe this has now been incorporated into the MAUI defaults? – Richard Hauer Jun 01 '22 at 01:09
  • `GetWebChromeClient()` doesn't exist anymore... – YaRmgl Feb 28 '23 at 10:52
3

Updated for 28/02/2023

Since the GetWebChromeClient() override does not exist anymore, I figured out a way around it, here is the tutorial:

Step 1: In Platforms/Android create a folder called Handlers, inside, create a class:

    internal class MyWebChromeClient : WebChromeClient
    {
        public override void OnPermissionRequest(PermissionRequest request)
        {
            // dirty code, fix this yourself
            try
            {
                request.Grant(request.GetResources());
                base.OnPermissionRequest(request);
            }
            catch (Exception ex) 
            {
                Console.WriteLine(ex);
            }
        }
    }

    public class MauiBlazorWebViewHandler : BlazorWebViewHandler
    {
        protected override global::Android.Webkit.WebView CreatePlatformView()
        {
            var view = base.CreatePlatformView();

            view.SetWebChromeClient(new MyWebChromeClient());

            return view;
        }
    }

Step 2: Inside MauiProgram.cs, add the following handler code:

#1 Using statement

#if ANDROID
using ClientAppHeavy.Platforms.Android.Handlers;
#endif

#2 Inside the method

            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                })
                .ConfigureMauiHandlers(handlers =>
                {
#if ANDROID
                    handlers.AddHandler<BlazorWebView, MauiBlazorWebViewHandler>();
#endif
                });

This will grant all permissions whenever requested, but the try/catch is there cause it calls Grant multiple times for some obscure reason, so I just did a quick and dirty workaround.

Good luck :)

YaRmgl
  • 356
  • 1
  • 6
  • 19
  • This seems to work almost for me. It works for my Javascript QR code library. But it does not work when I am using a regular HTML5 input element in order to capture images from the camera and/or filesystem. See this question: https://stackoverflow.com/questions/75784122/blazor-maui-capture-image-input – Smith5727 Mar 21 '23 at 15:31
1

Although the camera permission issue has been solved by @YaRmgl's answer, it will cause a side-effect that there is no response when clicking the HTML5 input element. The workaround is to override the OnShowFileChooser() function.

Here is the code:

using Android.Content;
using Android.Webkit;
using Microsoft.AspNetCore.Components.WebView.Maui;

namespace BarcodeScanner.Platforms.Android.Handlers
{
    public class MauiBlazorWebViewHandler : BlazorWebViewHandler
    {

        protected override global::Android.Webkit.WebView CreatePlatformView()
        {
            var view = base.CreatePlatformView();
            view.SetWebChromeClient(new MyWebChromeClient(this.Context));
            return view;
        }
    }

    internal class MyWebChromeClient : WebChromeClient
    {
        private MainActivity _activity;


        public MyWebChromeClient(Context context)
        {
            _activity = context as MainActivity;
        }

        public override void OnPermissionRequest(PermissionRequest request)
        {
            try
            {
                request.Grant(request.GetResources());
                base.OnPermissionRequest(request);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        public override bool OnShowFileChooser(global::Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams)
        {
            base.OnShowFileChooser(webView, filePathCallback, fileChooserParams);
            return _activity.ChooseFile(filePathCallback, fileChooserParams.CreateIntent(), fileChooserParams.Title);
        }


    }
}

The ChooseFile() method is implemented in the MainActivity.cs file:

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Webkit;
using static Android.Webkit.WebChromeClient;
using System.Diagnostics;

namespace BarcodeScanner;

[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
    private IValueCallback _filePathCallback;
    private int _requestCode = 100;

    protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
        if (_requestCode == requestCode)
        {
            if (_filePathCallback == null)
                return;

            Java.Lang.Object result = FileChooserParams.ParseResult((int)resultCode, data);
            _filePathCallback.OnReceiveValue(result);
        }
    }

    public bool ChooseFile(IValueCallback filePathCallback, Intent intent, string title)
    {
        _filePathCallback = filePathCallback;

        StartActivityForResult(Intent.CreateChooser(intent, title), _requestCode);

        return true;
    }
}

The full source code: https://github.com/yushulx/DotNet-MAUI-Blazor-Barcode-Scanner

yushulx
  • 11,695
  • 8
  • 37
  • 64
-1

I think you are missing some parts of the permission checks. You should read this section about runtime permissions that were introduced in Android 6.0. For example, I cannot see any override of OnRequestPermissionsResult in MainActivity.

https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/permissions?tabs=macos#runtime-permission-checks-in-android-60

Daniel
  • 1,537
  • 1
  • 13
  • 16