Eilon / MauiHybridWebView

MIT License
206 stars 45 forks source link

Need to modify the HybridWebView configuration (BEFORE initializing) on iOS/MacCatalyst to fix known base WebView issue ... Any solution? #67

Open jonmdev opened 3 months ago

jonmdev commented 3 months ago

There is a problem with the WebView in iOS/MacCatalyst where one must define parameters only on initialization for the ability to play media inline (ie. not go full screen every time you play), autoplay, and configure other media playback parameters.

This is described here:

The two solutions at those locations both don't work for HybridWebView, at least not directly without Reflection for the following reasons:

1) Blazor Fix

The BlazorWebView fix is shown here: https://github.com/dotnet/maui/issues/16013#issuecomment-1646456002

Where the user sets: BlazorWebViewInitializing="HandleWebViewInitializing"

    private void HandleWebViewInitializing(object sender, BlazorWebViewInitializingEventArgs args)
    {
        // WebView needs the configuration updated during initialization in order for "playsinline" attributes 
        // to work in the HTML5 video player for iOS devices.
#if IOS || MACCATALYST
        args.Configuration.AllowsInlineMediaPlayback = true;
        args.Configuration.MediaPlaybackRequiresUserAction = false;
        args.Configuration.RequiresUserActionForMediaPlayback = false;
#endif
    }

However, we cannot use this directly as the only event we have in HybridWebView is only run AFTER initialization (which is too late - I tried - config must be set before creation of the WebView and can't be affected after):

            void initFunction(object sender, HybridWebViewInitializedEventArgs args) {
#if IOS || MACCATALYST

                //doesn't work as already initialized on this event
                args.WebView.Configuration.AllowsInlineMediaPlayback = true;
                args.WebView.Configuration.MediaPlaybackRequiresUserAction = false;
                args.WebView.Configuration.RequiresUserActionForMediaPlayback = false;
#endif
            }

            HybridWebView.HybridWebView webView = new();
            webView.HybridWebViewInitialized += initFunction; //does not work as only runs AFTER INITIALIZATION        

2) WebView Solution

The WebView solution is given here: https://github.com/dotnet/maui/issues/4807#issuecomment-1299431970 and here: https://github.com/dotnet/maui/pull/11075 by @Redth:

There he suggests overriding the Microsoft.Maui.Handlers.WebViewHandler.PlatformViewFactory = (handler) => function. However, reviewing this in HybridWebView shows it is not so simple. The HybridWebView function is here:

However a similar approach doesn't work directly for numerous reasons:

Commenting out everything dependent on these breaks the HybridWebView.

3) Reflection Solution (WORKING)

I was just able to get a temporary fix using Reflection and redefining MessageReceived by modifying @Redth's solution approach:

#if IOS || MACCATALYST

            HybridWebView.HybridWebViewHandler.PlatformViewFactory = (handler) => {

                var config = new WKWebViewConfiguration();
                //====================================================
                //CUSTOM CONFIG
                config.AllowsAirPlayForMediaPlayback = true;
                config.AllowsInlineMediaPlayback = true;
                config.AllowsPictureInPictureMediaPlayback = true;
                config.MediaTypesRequiringUserActionForPlayback = WebKit.WKAudiovisualMediaTypes.None;

                //simple enough can just redundancy this one:
                Action<Uri, string> MessageReceivedAction = delegate (Uri uri, string message) { // to replace https://github.com/Eilon/MauiHybridWebView/blob/3ca801076a1e3fbe3b8922b2429524df20def6a4/HybridWebView/Platforms/iOS/HybridWebViewHandler.iOS.cs#L40
                    ((HybridWebView.HybridWebView)handler.VirtualView).OnMessageReceived(message);
                };

                //https://stackoverflow.com/a/39076814/10305478
                object CreatePrivateClassInstance(string typeName, object[] parameters) {
                    Type type = AppDomain.CurrentDomain.GetAssemblies().
                             SelectMany(assembly => assembly.GetTypes()).FirstOrDefault(t => t.Name == typeName);
                    return type.GetConstructors()[0].Invoke(parameters);
                }

                IWKScriptMessageHandler webViewScriptMessageHandler = (IWKScriptMessageHandler)CreatePrivateClassInstance("WebViewScriptMessageHandler", [MessageReceivedAction]);
                config.UserContentController.AddScriptMessageHandler(webViewScriptMessageHandler, "webwindowinterop");

                IWKUrlSchemeHandler? wKUrlSchemeHandler = (IWKUrlSchemeHandler?)CreatePrivateClassInstance("SchemeHandler", [handler as HybridWebViewHandler]);
                config.SetUrlSchemeHandler(wKUrlSchemeHandler, urlScheme: "app");
                //============================================

                //default programming built in
                //config.UserContentController.AddScriptMessageHandler(new WebViewScriptMessageHandler(MessageReceived), "webwindowinterop");
                //config.SetUrlSchemeHandler(new SchemeHandler(this), urlScheme: "app");

                // Legacy Developer Extras setting.
                var enableWebDevTools = ((HybridWebView.HybridWebView)(handler as HybridWebViewHandler).VirtualView).EnableWebDevTools;
                config.Preferences.SetValueForKey(NSObject.FromObject(enableWebDevTools), new NSString("developerExtrasEnabled"));
                var platformView = new MauiWKWebView(RectangleF.Empty, handler as HybridWebViewHandler, config);

                if (OperatingSystem.IsMacCatalystVersionAtLeast(major: 13, minor: 3) ||
                    OperatingSystem.IsIOSVersionAtLeast(major: 16, minor: 4)) {
                    // Enable Developer Extras for Catalyst/iOS builds for 16.4+
                    platformView.SetValueForKey(NSObject.FromObject(enableWebDevTools), new NSString("inspectable"));
                }

                return platformView;
            };
#endif

I tested and this does seem to work. I have an inline video autoplaying now.

Long Term Solution?

The simplest solution I can think of would be to just provide us with an Action<WKWebViewConfiguration> customConfiguration in iOS/MacCatalyst we can set into the HybridWebView or the Handler somehow before this function is run. It can get utilized inside that existing function like this:

        protected override WKWebView CreatePlatformView() {

                var config = new WKWebViewConfiguration();
                //=================================================
                customConfiguration?.Invoke(config); //modify the config file on the way through here
                //=================================================

                //then continue default programming built in
                config.UserContentController.AddScriptMessageHandler(new HybridWebView.WebViewScriptMessageHandler(MessageReceived), "webwindowinterop");
                config.SetUrlSchemeHandler(new SchemeHandler(this), urlScheme: "app");

Or otherwise I am not sure how you guys would want to fix this.

What do you think @Eilon ? Or @Redth ?

HelloooJoe commented 3 months ago

@Eilon Can you consider inviting other developers help maintain this repo that have contributed already? It seems like you're not giving this repo enough attention.

Eilon commented 2 months ago

@Eilon Can you consider inviting other developers help maintain this repo that have contributed already? It seems like you're not giving this repo enough attention.

I've been working on the official version here: https://github.com/Eilon/MauiHybridWebView/issues/70

So for that reason my time hasn't been as focused here. Having said that, I'm certainly willing to work with people on features, fixes, and PRs within this repo for features that are good candidates to be in the official version.

jonmdev commented 2 months ago

@Eilon Can you consider inviting other developers help maintain this repo that have contributed already? It seems like you're not giving this repo enough attention.

I've been working on the official version here: #70

So for that reason my time hasn't been as focused here. Having said that, I'm certainly willing to work with people on features, fixes, and PRs within this repo for features that are good candidates to be in the official version.

Congratulations on being officially included. Please keep in mind the issue reported in this thread, and if nothing else, please don't break this method without providing us an alternative.

Thanks again.

#if IOS || MACCATALYST

            HybridWebView.HybridWebViewHandler.PlatformViewFactory = (handler) => {

                var config = new WKWebViewConfiguration();
                //====================================================
                //CUSTOM CONFIG
                config.AllowsAirPlayForMediaPlayback = true;
                config.AllowsInlineMediaPlayback = true;
                config.AllowsPictureInPictureMediaPlayback = true;
                config.MediaTypesRequiringUserActionForPlayback = WebKit.WKAudiovisualMediaTypes.None;

                //simple enough can just redundancy this one:
                Action<Uri, string> MessageReceivedAction = delegate (Uri uri, string message) { // to replace https://github.com/Eilon/MauiHybridWebView/blob/3ca801076a1e3fbe3b8922b2429524df20def6a4/HybridWebView/Platforms/iOS/HybridWebViewHandler.iOS.cs#L40
                    ((HybridWebView.HybridWebView)handler.VirtualView).OnMessageReceived(message);
                };

                //https://stackoverflow.com/a/39076814/10305478
                object CreatePrivateClassInstance(string typeName, object[] parameters) {
                    Type type = AppDomain.CurrentDomain.GetAssemblies().
                             SelectMany(assembly => assembly.GetTypes()).FirstOrDefault(t => t.Name == typeName);
                    return type.GetConstructors()[0].Invoke(parameters);
                }

                IWKScriptMessageHandler webViewScriptMessageHandler = (IWKScriptMessageHandler)CreatePrivateClassInstance("WebViewScriptMessageHandler", [MessageReceivedAction]);
                config.UserContentController.AddScriptMessageHandler(webViewScriptMessageHandler, "webwindowinterop");

                IWKUrlSchemeHandler? wKUrlSchemeHandler = (IWKUrlSchemeHandler?)CreatePrivateClassInstance("SchemeHandler", [handler as HybridWebViewHandler]);
                config.SetUrlSchemeHandler(wKUrlSchemeHandler, urlScheme: "app");
                //============================================

                //default programming built in
                //config.UserContentController.AddScriptMessageHandler(new WebViewScriptMessageHandler(MessageReceived), "webwindowinterop");
                //config.SetUrlSchemeHandler(new SchemeHandler(this), urlScheme: "app");

                // Legacy Developer Extras setting.
                var enableWebDevTools = ((HybridWebView.HybridWebView)(handler as HybridWebViewHandler).VirtualView).EnableWebDevTools;
                config.Preferences.SetValueForKey(NSObject.FromObject(enableWebDevTools), new NSString("developerExtrasEnabled"));
                var platformView = new MauiWKWebView(RectangleF.Empty, handler as HybridWebViewHandler, config);

                if (OperatingSystem.IsMacCatalystVersionAtLeast(major: 13, minor: 3) ||
                    OperatingSystem.IsIOSVersionAtLeast(major: 16, minor: 4)) {
                    // Enable Developer Extras for Catalyst/iOS builds for 16.4+
                    platformView.SetValueForKey(NSObject.FromObject(enableWebDevTools), new NSString("inspectable"));
                }

                return platformView;
            };
#endif