pichillilorenzo / flutter_inappwebview

A Flutter plugin that allows you to add an inline webview, to use a headless webview, and to open an in-app browser window.
https://inappwebview.dev
Apache License 2.0
3.3k stars 1.64k forks source link

Unable to Play Amazon Prime Video #624

Closed ycv005 closed 3 years ago

ycv005 commented 3 years ago

Environment

Technology Version
Flutter version 1.22.4 + 1.22.5
Plugin version 5.0 (getting from master branch of github)
Android version 10
iOS version NA
Xcode version NA

Device information: Realme 3 Pro

Description

When I am running the Amazon Prime Video it is not playing. I have tried with option mediaPlaybackRequiresUserGesture and other options as well which sounds near to it, but none of them are working. Whereas I am able to watch the only Trailer on it.

Expected behavior:

Current behavior:

Steps to reproduce

cc- @pichillilorenzo @tneotia @plateaukao @Doflatango

Here is the code-

class WebApp extends StatefulWidget {
  .....
  @override
  _WebAppState createState() => _WebAppState();
}

class _WebAppState extends State<WebApp>
    with AutomaticKeepAliveClientMixin<WebApp> {
  @override
  bool get wantKeepAlive => true;
  String currentUrl, userAgent;
  InAppWebViewController controller;
  bool isLoading = true;
  int reTryCount = 2;
  FirebaseService firebaseService = FirebaseService();
  bool showAlertDialog = false;

  @override
  void initState() {
    super.initState();
    setUserAgent();
  }

  setUserAgent() async {
    userAgent = await Constants.getUserAgent();
    print("here we set useragent- $userAgent");
  }
  @override
  Widget build(BuildContext context) {
    super.build(context);
    Widget mainWidget = Stack(
      children: [
        Column(
          children: <Widget>[
            Expanded(
              child: InAppWebView(
                initialUrl: "https://auth.uber.com/login/",
                onWebViewCreated: (InAppWebViewController webViewController) {
                  controller = webViewController;
                },
                onLoadStart:
                    (InAppWebViewController webViewController, String url) {
                  this.currentUrl = url;
                },
                onLoadStop: (controller, url) {
                  if (isLoading == true) {
                    setState(() {
                      isLoading = false;
                    });
                  }
                },
                onEnterFullscreen: (controller) async {
                    await SystemChrome.setPreferredOrientations([
                      DeviceOrientation.landscapeRight,
                      DeviceOrientation.landscapeLeft,
                    ]);
                },
                onExitFullscreen: (controller) async {
                    await SystemChrome.setPreferredOrientations([
                      DeviceOrientation.portraitDown,
                      DeviceOrientation.portraitUp,
                      DeviceOrientation.landscapeRight,
                      DeviceOrientation.landscapeLeft,
                    ]);
                },
                initialOptions: InAppWebViewGroupOptions(
                  android: AndroidInAppWebViewOptions(
                    disableDefaultErrorPage: true,
                    useHybridComposition: true,
                    supportMultipleWindows: true,
                  ),
                  crossPlatform: InAppWebViewOptions(
                    mediaPlaybackRequiresUserGesture: false,
                    horizontalScrollBarEnabled: false,
                    useShouldOverrideUrlLoading: true,
                    javaScriptCanOpenWindowsAutomatically: true,
                    verticalScrollBarEnabled: false,
                  ),
                ),
                onCreateWindow: (controller, createWindowRequest) async {
                  showDialog(
                    useSafeArea: true,
                    context: context,
                    builder: (context) {
                      return AlertDialog(
                        content: Container(
                          width: MediaQuery.of(context).size.width,
                          height: 400,
                          child: InAppWebView(
                            windowId: createWindowRequest.windowId,
                            initialOptions: InAppWebViewGroupOptions(
                            android: AndroidInAppWebViewOptions(
                                disableDefaultErrorPage: true,
                                useHybridComposition: true,
                                 supportMultipleWindows: true,
                              ),
                              crossPlatform: InAppWebViewOptions(
                               mediaPlaybackRequiresUserGesture: false,
                               horizontalScrollBarEnabled: false,
                               userAgent: userAgent,
                               useShouldOverrideUrlLoading: true,
                              verticalScrollBarEnabled: false,
                               javaScriptCanOpenWindowsAutomatically: true,
                              ),
                            ),
                            onCloseWindow: (controller) {
                              // On Facebook Login, this event is called twice,
                              // so here we check if we already popped the alert dialog context
                              if (Navigator.canPop(context)) {
                                Navigator.pop(context);
                              }
                            },
                          ),
                        ),
                      );
                    },
                  );
                  return true;
                },
                shouldOverrideUrlLoading:
                    (controller, shouldOverrideUrlLoadingRequest) async {
                  final String url = shouldOverrideUrlLoadingRequest.url;
                  if (!url.startsWith("http")) {
                    if (!url.startsWith("intent")) {
                      final String newUrl = await controller?.getUrl();
                        Constants.launchUrl(url);
                    }
                    return ShouldOverrideUrlLoadingAction.CANCEL;
                  }
                  return ShouldOverrideUrlLoadingAction.ALLOW;
                },
                androidOnPermissionRequest: (InAppWebViewController controller,
                    String origin, List<String> resources) async {
                  return PermissionRequestResponse(
                      resources: resources,
                      action: PermissionRequestResponseAction.GRANT);
                },
                androidOnGeolocationPermissionsShowPrompt:
                    (InAppWebViewController controller, String origin) async {
                  if (await Permission.locationWhenInUse.isDenied) {
                    await Permission.location.request();
                  } else if (await Permission
                      .locationWhenInUse.isPermanentlyDenied) {
                    openAppSettings();
                  }
                  return GeolocationPermissionShowPromptResponse(
                      origin: origin, allow: true, retain: true);
                },
              ),
            ),
            Container(
              color: Colors.white,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  WebviewBottomButton(
                    onPressed: () => Navigator.of(context).pop(),
                    iconData: Icons.exit_to_app,
                  ),
                  WebviewBottomButton(
                    onPressed: () {
                      if (controller != null) {
                        controller.reload();
                      }
                    },
                    iconData: Icons.refresh,
                  ),
                  WebviewBottomButton(
                    onPressed: () async {
                      final String currentUrl = await controller.getUrl();
                      final String msg =
                          await shareUrl(currentUrl, "${widget.app}_app");
                      Share.share(msg);
                    },
                    iconData: Icons.share,
                  ),
                  WebviewBottomButton(
                    onPressed: () => _handleBack(context, widget.app),
                    iconData: Icons.arrow_back,
                  ),
                  WebviewBottomButton(
                    onPressed: () {
                      if (controller != null) {
                        controller.goForward();
                      }
                    },
                    iconData: Icons.arrow_forward,
                  ),
                ],
              ),
            ),
          ],
        ),
        if (isLoading) CircularProgressIndicator(),
      ],
    );
    return widget.forWidget == 'single_app'
        ? Scaffold(
            body: SafeArea(
                top: true,
                child: WillPopScope(
                  onWillPop: () => _handleBack(context, widget.app),
                  child: mainWidget,
                )),
          )
        : mainWidget;
  }
}

class WebviewBottomButton extends StatelessWidget {
  final double minWidth;
  final Function onPressed;
  final IconData iconData;
  const WebviewBottomButton({
    this.minWidth = 0,
    @required this.onPressed,
    @required this.iconData,
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialButton(
      minWidth: minWidth,
      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
      padding: EdgeInsets.zero,
      child: Icon(iconData),
      onPressed: onPressed,
    );
  }
}
  1. This
  2. Than that
  3. Then

Images

Stacktrace/Logcat

tneotia commented 3 years ago

It's likely not an issue with the plugin, just that Widevine (software that Netflix, Amazon Prime Video, Hulu, etc) extension/plugin isn't supported in Android WebView so premium video content will not play. You might be able to try this within the inappwebview plugin code and see if it works.

ycv005 commented 3 years ago

First of all very thanks to @tneotia but stucked as mentioned below

In the file -( or go to exact line of file)

Code File link- https://github.com/pichillilorenzo/flutter_inappwebview/blob/master/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java

I wrote like this

      webView.setWebChromeClient(new WebChromeClient(){
        @Override
        public void onPermissionRequest(PermissionRequest request) {
            String[] resources = request.getResources();
            for (int i = 0; i < resources.length; i++) {
                if (PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID.equals(resources[i])) {
                    request.grant(resources);
                    return;
                }
            }
            super.onPermissionRequest(request);
        }
      });

But when I am running the app with or without debugging, I am getting below error message-

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':flutter_inappwebview:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1m 6s
Exception: Gradle task assembleDebug failed with exit code 1

What to do next @tneotia ?

Also, I would love if other ( @pichillilorenzo @plateaukao @Doflatango ) raise PR related to this.

tneotia commented 3 years ago

You put it in the wrong file, it should be this one: https://github.com/pichillilorenzo/flutter_inappwebview/blob/master/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java

If you scroll all the way down you will actually see the permission override implemented , just try and replace the code in there with the stackoverflow answer and see if it works. Then we can find a way to combine the current and the stackoverflow.

ycv005 commented 3 years ago

Note- Test on the 2 device, realme 3 pro, redmi k20 pro, both has widevine l1 license.

This time builds successfully after running flutter clean

I have replaced as following but didn't work, but this time I am getting some new logs

  @Override
  public void onPermissionRequest(final PermissionRequest request) {
    String[] resources = request.getResources();
    for (int i = 0; i < resources.length; i++) {
        if (PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID.equals(resources[i])) {
            request.grant(resources);
            return;
        }
    }
    // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    //   Map<String, Object> obj = new HashMap<>();
    //   if (inAppBrowserActivity != null)
    //     obj.put("uuid", inAppBrowserActivity.uuid);
    //   obj.put("origin", request.getOrigin().toString());
    //   obj.put("resources", Arrays.asList(request.getResources()));
    //   channel.invokeMethod("onPermissionRequest", obj, new MethodChannel.Result() {
    //     @Override
    //     public void success(Object response) {
    //       if (response != null) {
    //         Map<String, Object> responseMap = (Map<String, Object>) response;
    //         Integer action = (Integer) responseMap.get("action");
    //         List<String> resourceList = (List<String>) responseMap.get("resources");
    //         if (resourceList == null)
    //           resourceList = new ArrayList<String>();
    //         String[] resources = new String[resourceList.size()];
    //         resources = resourceList.toArray(resources);
    //         if (action != null) {
    //           switch (action) {
    //             case 1:
    //               request.grant(resources);
    //               return;
    //             case 0:
    //             default:
    //               request.deny();
    //               return;
    //           }
    //         }
    //       }
    //       request.deny();
    //     }

    //     @Override
    //     public void error(String s, String s1, Object o) {
    //       Log.e(LOG_TAG, s + ", " + s1);
    //     }

    //     @Override
    //     public void notImplemented() {
    //       request.deny();
    //     }
    //   });
    // }
  }

Major Logs which I thought are relevant (on Redmi K20 Pro)-

W/VideoCapabilities( 3922): Unsupported mime image/vnd.android.heic
W/VideoCapabilities( 3922): Unsupported mime video/divx
W/VideoCapabilities( 3922): Unsupported mime video/divx4
2
W/VideoCapabilities( 3922): Unrecognized profile/level 0/3 for video/mpeg2
W/VideoCapabilities( 3922): Unsupported mime video/x-ms-wmv
I/cr_media( 3922): Create MediaDrmBridge with level L3 and origin CFC601DDA4B85A7E071E6240A437A8D2
I/MediaDrm( 3922): Drm event (1,0)
E/cr_media( 3922): EventListener: No session for event 1.
I/cr_media( 3922): Provisioning origin ID CFC601DDA4B85A7E071E6240A437A8D2
I/AAudio  ( 3922): AAudioStreamBuilder_openStream() called ----------------------------------------
I/AudioStreamBuilder( 3922): rate   =  48000, channels  = 2, format   = 5, sharing = SH, dir = OUTPUT
I/AudioStreamBuilder( 3922): device =      0, sessionId = -1, perfMode = 11, callback: ON with frames = 1024
I/AudioStreamBuilder( 3922): usage  =      1, contentType = 0, inputPreset = 0, allowedCapturePolicy = 0
D/AudioStreamBuilder( 3922): build() MMAP not available because AAUDIO_PERFORMANCE_MODE_LOW_LATENCY not used.
D/        ( 3922): PlayerBase::PlayerBase()
D/AudioStreamTrack( 3922): open(), request notificationFrames = 1024, frameCount = 1024

Edited - Logs on the Realme 3 Pro

V/SensorManager(31009): RegisterListener Game Rotation Vector  Non-wakeup type:15 delay:16666us by org.chromium.device.sensors.PlatformSensor
I/Choreographer(31009): Skipped 4 frames!  The application may be doing too much work on its main thread.
I/cr_media(31009): Create MediaDrmBridge with level L3 and origin 2B70C71EB482FBAC1C64C550A6961CE2
V/SensorManager(31009): unRegisterListener by org.chromium.device.sensors.PlatformSensor
I/Choreographer(31009): Skipped 4 frames!  The application may be doing too much work on its main thread.

@tneotia what to do now ?

tneotia commented 3 years ago

Strange... It looks like it is provisioning the DRM content based on the logs. Just for the sake of checking can you load https://html5test.com/ and check in the "Streaming" section if DRM is marked supported?

ycv005 commented 3 years ago

@tneotia Here is the output- https://photos.app.goo.gl/YJQKET24Fr2taHPT8

Edited- There is no DRM section in Stream section but Writable streams is not supported. How to make it supportable ?

Edited- Where as other streaming paltform videos are playable.

ycv005 commented 3 years ago

Strange... It looks like it is provisioning the DRM content based on the logs. Just for the sake of checking can you load https://html5test.com/ and check in the "Streaming" section if DRM is marked supported?

Hey, I got under the Streaming section, DRM is supported. @tneotia

tneotia commented 3 years ago

Alright. Now try this url https://bitmovin.com/demos/drm and see if it says using Widevine above video player (and video is playable)

ycv005 commented 3 years ago

Alright. Now try this url https://bitmovin.com/demos/drm and see if it says using Widevine above video player (and video is playable)

It says Detected, using NO DRM. where as in Android Chrome it was playing a video with audio.

image

Also, with this link, I think, anyone can easily test the DRM compatibility.

ycv005 commented 3 years ago

Any idea @tneotia , have you tried.

tneotia commented 3 years ago

I have no idea honestly... I think someone more experienced with webview should chime in. If you are allowing the permission I don't see why Widevine wouldn't be supported, unless your phone itself doesn't support DRM. I guess you can check that by going into Chrome and checking the same bitmovin website and seeing if it detects DRM.

On my S10+ I get a popup saying "bitmovin wants to play protected content, your device's identity will be verified", and when I tap allow it switches to widevine.

ycv005 commented 3 years ago

In my chrome, I am showing a webplay with a video saying Detected chrome, using widevine. By the way, so much thanks to you. Highly appreciated. I don't know where is @pichillilorenzo from very long time.

image

ycv005 commented 3 years ago

In my chrome, I am showing a webplay with a video saying Detected chrome, using widevine. By the way, so much thanks to you. Highly appreciated. I don't know where is @pichillilorenzo from very long time.

image

@pichillilorenzo ?

pichillilorenzo commented 3 years ago

I just tried using the GitHub master version of the plugin on the emulator (Android 11) and https://bitmovin.com/demos/drm can detect widevine. It isn't necessary to modify the plugin code directly. As you can see, the Java onPermissionRequest method has been already overridden by me in order to make it visible on Flutter side. You can listen to that event through InAppWebView.androidOnPermissionRequest event, for example:

InAppWebView(
  initialUrl: "https://bitmovin.com/demos/drm",
  initialOptions: InAppWebViewGroupOptions(
    android: AndroidInAppWebViewOptions(
      useHybridComposition: true
    )
  ),
  androidOnPermissionRequest: (controller, origin, resources) async {
    return PermissionRequestResponse(resources: resources, action: PermissionRequestResponseAction.GRANT);;
  },
)

Schermata 2021-01-30 alle 15 04 01

ycv005 commented 3 years ago

Hey @pichillilorenzo , the following code snippet is already written into my code, but still unable to Play Amazon Prime's Video whereas I am able to run same video in the Chrome. Also, widevine is detect into my chrome as well as in_appwebview.

InAppWebView(
  initialUrl: "https://www.primevideo.com/",
  initialOptions: InAppWebViewGroupOptions(
    android: AndroidInAppWebViewOptions(
      useHybridComposition: true
    )
  ),
  androidOnPermissionRequest: (controller, origin, resources) async {
    return PermissionRequestResponse(resources: resources, action: PermissionRequestResponseAction.GRANT);;
  },
)

Any direction will be helpful. thanks

ycv005 commented 3 years ago

@pichillilorenzo Why Amazon prime's video runs in chrome and not in this webview. (widevine is detected then also).

pichillilorenzo commented 3 years ago

Just found the answer on StackOverflow: https://stackoverflow.com/a/64245469/4637638

The DRM used for a given service is designed to only deliver the licensee to an app or devices which has been verified to be associated with an authorised logged in user.

In other words the DRM encrypts the licensee (which includes the content key itself) it is delivering with a key which the authorised device or client can decrypt, and which others can't.

Unless you have a partnering agreement or similar with the service provider you will not be authorised like this, so, unless you plan in pirating the content, can't play the video.

github-actions[bot] commented 2 weeks ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug and a minimal reproduction of the issue.