Skip to content

Commit

Permalink
[webview_flutter_android] Added the functionality to fullscreen html5…
Browse files Browse the repository at this point in the history
… video (#3879)

At this moment on android the fullscreen html5 video does not work. This PR solves that issues, adding the functionality that the video is played fullscreen when you click on the fullscreen-button in the video player.

Fixes flutter/flutter#27101

- [ x I read and followed the [relevant style guides] and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages repo does use `dart format`.)
  • Loading branch information
paulppn authored Sep 8, 2023
1 parent aaae5ef commit 3800f6d
Show file tree
Hide file tree
Showing 33 changed files with 1,911 additions and 88 deletions.
5 changes: 5 additions & 0 deletions packages/webview_flutter/webview_flutter_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.10.0

* Adds support for playing video in fullscreen. See
`AndroidWebViewController.setCustomWidgetCallbacks`.

## 3.9.5

* Updates pigeon to 11 and removes unneeded enum wrappers.
Expand Down
23 changes: 22 additions & 1 deletion packages/webview_flutter/webview_flutter_android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ This can be configured for versions >=23 with
`AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features
for more details on setting platform-specific features in the main plugin.

### External Native API
## External Native API

The plugin also provides a native API accessible by the native code of Android applications or
packages. This API follows the convention of breaking changes of the Dart API, which means that any
Expand All @@ -52,6 +52,27 @@ Java:
import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi;
```

## Fullscreen Video

To display a video as fullscreen, an app must manually handle the notification that the current page
has entered fullscreen mode. This can be done by calling
`AndroidWebViewController.setCustomWidgetCallbacks`. Below is an example implementation.

<?code-excerpt "example/lib/main.dart (fullscreen_example)"?>
```dart
androidController.setCustomWidgetCallbacks(
onShowCustomWidget: (Widget widget, OnHideCustomWidgetCallback callback) {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) => widget,
fullscreenDialog: true,
));
},
onHideCustomWidget: () {
Navigator.of(context).pop();
},
);
```

## Contributing

This package uses [pigeon][3] to generate the communication layer between Flutter and the host
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import android.webkit.WebChromeClient.CustomViewCallback;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CustomViewCallbackFlutterApi;

/**
* Flutter API implementation for `CustomViewCallback`.
*
* <p>This class may handle adding native instances that are attached to a Dart instance or passing
* arguments of callbacks methods to a Dart instance.
*/
public class CustomViewCallbackFlutterApiImpl {
// To ease adding additional methods, this value is added prematurely.
@SuppressWarnings({"unused", "FieldCanBeLocal"})
private final BinaryMessenger binaryMessenger;

private final InstanceManager instanceManager;
private CustomViewCallbackFlutterApi api;

/**
* Constructs a {@link CustomViewCallbackFlutterApiImpl}.
*
* @param binaryMessenger used to communicate with Dart over asynchronous messages
* @param instanceManager maintains instances stored to communicate with attached Dart objects
*/
public CustomViewCallbackFlutterApiImpl(
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
this.binaryMessenger = binaryMessenger;
this.instanceManager = instanceManager;
api = new CustomViewCallbackFlutterApi(binaryMessenger);
}

/**
* Stores the `CustomViewCallback` instance and notifies Dart to create and store a new
* `CustomViewCallback` instance that is attached to this one. If `instance` has already been
* added, this method does nothing.
*/
public void create(
@NonNull CustomViewCallback instance,
@NonNull CustomViewCallbackFlutterApi.Reply<Void> callback) {
if (!instanceManager.containsInstance(instance)) {
api.create(instanceManager.addHostCreatedInstance(instance), callback);
}
}

/**
* Sets the Flutter API used to send messages to Dart.
*
* <p>This is only visible for testing.
*/
@VisibleForTesting
void setApi(@NonNull CustomViewCallbackFlutterApi api) {
this.api = api;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import android.webkit.WebChromeClient.CustomViewCallback;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CustomViewCallbackHostApi;
import java.util.Objects;

/**
* Host API implementation for `CustomViewCallback`.
*
* <p>This class may handle instantiating and adding native object instances that are attached to a
* Dart instance or handle method calls on the associated native class or an instance of the class.
*/
public class CustomViewCallbackHostApiImpl implements CustomViewCallbackHostApi {
// To ease adding additional methods, this value is added prematurely.
@SuppressWarnings({"unused", "FieldCanBeLocal"})
private final BinaryMessenger binaryMessenger;

private final InstanceManager instanceManager;

/**
* Constructs a {@link CustomViewCallbackHostApiImpl}.
*
* @param binaryMessenger used to communicate with Dart over asynchronous messages
* @param instanceManager maintains instances stored to communicate with attached Dart objects
*/
public CustomViewCallbackHostApiImpl(
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
this.binaryMessenger = binaryMessenger;
this.instanceManager = instanceManager;
}

@Override
public void onCustomViewHidden(@NonNull Long identifier) {
getCustomViewCallbackInstance(identifier).onCustomViewHidden();
}

private CustomViewCallback getCustomViewCallbackInstance(@NonNull Long identifier) {
return Objects.requireNonNull(instanceManager.getInstance(identifier));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
package io.flutter.plugins.webviewflutter;

import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;

class FlutterWebViewFactory extends PlatformViewFactory {
class FlutterViewFactory extends PlatformViewFactory {
private final InstanceManager instanceManager;

FlutterWebViewFactory(InstanceManager instanceManager) {
FlutterViewFactory(InstanceManager instanceManager) {
super(StandardMessageCodec.INSTANCE);
this.instanceManager = instanceManager;
}
Expand All @@ -24,13 +25,26 @@ class FlutterWebViewFactory extends PlatformViewFactory {
public PlatformView create(Context context, int viewId, @Nullable Object args) {
final Integer identifier = (Integer) args;
if (identifier == null) {
throw new IllegalStateException("An identifier is required to retrieve WebView instance.");
throw new IllegalStateException("An identifier is required to retrieve a View instance.");
}

final PlatformView view = instanceManager.getInstance(identifier);
if (view == null) {
throw new IllegalStateException("Unable to find WebView instance: " + args);
final Object instance = instanceManager.getInstance(identifier);

if (instance instanceof PlatformView) {
return (PlatformView) instance;
} else if (instance instanceof View) {
return new PlatformView() {
@Override
public View getView() {
return (View) instance;
}

@Override
public void dispose() {}
};
}
return view;

throw new IllegalStateException(
"Unable to find a PlatformView or View instance: " + args + ", " + instance);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2602,6 +2602,33 @@ public void onPermissionRequest(
new ArrayList<Object>(Arrays.asList(instanceIdArg, requestInstanceIdArg)),
channelReply -> callback.reply(null));
}
/** Callback to Dart function `WebChromeClient.onShowCustomView`. */
public void onShowCustomView(
@NonNull Long instanceIdArg,
@NonNull Long viewIdentifierArg,
@NonNull Long callbackIdentifierArg,
@NonNull Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onShowCustomView",
getCodec());
channel.send(
new ArrayList<Object>(
Arrays.asList(instanceIdArg, viewIdentifierArg, callbackIdentifierArg)),
channelReply -> callback.reply(null));
}
/** Callback to Dart function `WebChromeClient.onHideCustomView`. */
public void onHideCustomView(@NonNull Long instanceIdArg, @NonNull Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onHideCustomView",
getCodec());
channel.send(
new ArrayList<Object>(Collections.singletonList(instanceIdArg)),
channelReply -> callback.reply(null));
}
/** Callback to Dart function `WebChromeClient.onGeolocationPermissionsShowPrompt`. */
public void onGeolocationPermissionsShowPrompt(
@NonNull Long instanceIdArg,
Expand Down Expand Up @@ -2867,6 +2894,137 @@ public void create(
channelReply -> callback.reply(null));
}
}
/**
* Host API for `CustomViewCallback`.
*
* <p>This class may handle instantiating and adding native object instances that are attached to
* a Dart instance or handle method calls on the associated native class or an instance of the
* class.
*
* <p>See
* https://developer.android.com/reference/android/webkit/WebChromeClient.CustomViewCallback.
*
* <p>Generated interface from Pigeon that represents a handler of messages from Flutter.
*/
public interface CustomViewCallbackHostApi {
/** Handles Dart method `CustomViewCallback.onCustomViewHidden`. */
void onCustomViewHidden(@NonNull Long identifier);

/** The codec used by CustomViewCallbackHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
}
/**
* Sets up an instance of `CustomViewCallbackHostApi` to handle messages through the
* `binaryMessenger`.
*/
static void setup(
@NonNull BinaryMessenger binaryMessenger, @Nullable CustomViewCallbackHostApi api) {
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.CustomViewCallbackHostApi.onCustomViewHidden",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
try {
api.onCustomViewHidden(
(identifierArg == null) ? null : identifierArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/**
* Flutter API for `CustomViewCallback`.
*
* <p>This class may handle instantiating and adding Dart instances that are attached to a native
* instance or receiving callback methods from an overridden native class.
*
* <p>See
* https://developer.android.com/reference/android/webkit/WebChromeClient.CustomViewCallback.
*
* <p>Generated class from Pigeon that represents Flutter messages that can be called from Java.
*/
public static class CustomViewCallbackFlutterApi {
private final @NonNull BinaryMessenger binaryMessenger;

public CustomViewCallbackFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
this.binaryMessenger = argBinaryMessenger;
}

/** Public interface for sending reply. */
@SuppressWarnings("UnknownNullness")
public interface Reply<T> {
void reply(T reply);
}
/** The codec used by CustomViewCallbackFlutterApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
}
/** Create a new Dart instance and add it to the `InstanceManager`. */
public void create(@NonNull Long identifierArg, @NonNull Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.CustomViewCallbackFlutterApi.create",
getCodec());
channel.send(
new ArrayList<Object>(Collections.singletonList(identifierArg)),
channelReply -> callback.reply(null));
}
}
/**
* Flutter API for `View`.
*
* <p>This class may handle instantiating and adding Dart instances that are attached to a native
* instance or receiving callback methods from an overridden native class.
*
* <p>See https://developer.android.com/reference/android/view/View.
*
* <p>Generated class from Pigeon that represents Flutter messages that can be called from Java.
*/
public static class ViewFlutterApi {
private final @NonNull BinaryMessenger binaryMessenger;

public ViewFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
this.binaryMessenger = argBinaryMessenger;
}

/** Public interface for sending reply. */
@SuppressWarnings("UnknownNullness")
public interface Reply<T> {
void reply(T reply);
}
/** The codec used by ViewFlutterApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
}
/** Create a new Dart instance and add it to the `InstanceManager`. */
public void create(@NonNull Long identifierArg, @NonNull Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.ViewFlutterApi.create",
getCodec());
channel.send(
new ArrayList<Object>(Collections.singletonList(identifierArg)),
channelReply -> callback.reply(null));
}
}
/**
* Host API for `GeolocationPermissionsCallback`.
*
Expand Down
Loading

0 comments on commit 3800f6d

Please sign in to comment.