Skip to content

Commit 3800f6d

Browse files
authored
[webview_flutter_android] Added the functionality to fullscreen html5 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`.)
1 parent aaae5ef commit 3800f6d

33 files changed

+1911
-88
lines changed

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 3.10.0
2+
3+
* Adds support for playing video in fullscreen. See
4+
`AndroidWebViewController.setCustomWidgetCallbacks`.
5+
16
## 3.9.5
27

38
* Updates pigeon to 11 and removes unneeded enum wrappers.

packages/webview_flutter/webview_flutter_android/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ This can be configured for versions >=23 with
3636
`AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features
3737
for more details on setting platform-specific features in the main plugin.
3838

39-
### External Native API
39+
## External Native API
4040

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

55+
## Fullscreen Video
56+
57+
To display a video as fullscreen, an app must manually handle the notification that the current page
58+
has entered fullscreen mode. This can be done by calling
59+
`AndroidWebViewController.setCustomWidgetCallbacks`. Below is an example implementation.
60+
61+
<?code-excerpt "example/lib/main.dart (fullscreen_example)"?>
62+
```dart
63+
androidController.setCustomWidgetCallbacks(
64+
onShowCustomWidget: (Widget widget, OnHideCustomWidgetCallback callback) {
65+
Navigator.of(context).push(MaterialPageRoute<void>(
66+
builder: (BuildContext context) => widget,
67+
fullscreenDialog: true,
68+
));
69+
},
70+
onHideCustomWidget: () {
71+
Navigator.of(context).pop();
72+
},
73+
);
74+
```
75+
5576
## Contributing
5677

5778
This package uses [pigeon][3] to generate the communication layer between Flutter and the host
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import android.webkit.WebChromeClient.CustomViewCallback;
8+
import androidx.annotation.NonNull;
9+
import androidx.annotation.VisibleForTesting;
10+
import io.flutter.plugin.common.BinaryMessenger;
11+
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CustomViewCallbackFlutterApi;
12+
13+
/**
14+
* Flutter API implementation for `CustomViewCallback`.
15+
*
16+
* <p>This class may handle adding native instances that are attached to a Dart instance or passing
17+
* arguments of callbacks methods to a Dart instance.
18+
*/
19+
public class CustomViewCallbackFlutterApiImpl {
20+
// To ease adding additional methods, this value is added prematurely.
21+
@SuppressWarnings({"unused", "FieldCanBeLocal"})
22+
private final BinaryMessenger binaryMessenger;
23+
24+
private final InstanceManager instanceManager;
25+
private CustomViewCallbackFlutterApi api;
26+
27+
/**
28+
* Constructs a {@link CustomViewCallbackFlutterApiImpl}.
29+
*
30+
* @param binaryMessenger used to communicate with Dart over asynchronous messages
31+
* @param instanceManager maintains instances stored to communicate with attached Dart objects
32+
*/
33+
public CustomViewCallbackFlutterApiImpl(
34+
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
35+
this.binaryMessenger = binaryMessenger;
36+
this.instanceManager = instanceManager;
37+
api = new CustomViewCallbackFlutterApi(binaryMessenger);
38+
}
39+
40+
/**
41+
* Stores the `CustomViewCallback` instance and notifies Dart to create and store a new
42+
* `CustomViewCallback` instance that is attached to this one. If `instance` has already been
43+
* added, this method does nothing.
44+
*/
45+
public void create(
46+
@NonNull CustomViewCallback instance,
47+
@NonNull CustomViewCallbackFlutterApi.Reply<Void> callback) {
48+
if (!instanceManager.containsInstance(instance)) {
49+
api.create(instanceManager.addHostCreatedInstance(instance), callback);
50+
}
51+
}
52+
53+
/**
54+
* Sets the Flutter API used to send messages to Dart.
55+
*
56+
* <p>This is only visible for testing.
57+
*/
58+
@VisibleForTesting
59+
void setApi(@NonNull CustomViewCallbackFlutterApi api) {
60+
this.api = api;
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import android.webkit.WebChromeClient.CustomViewCallback;
8+
import androidx.annotation.NonNull;
9+
import io.flutter.plugin.common.BinaryMessenger;
10+
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CustomViewCallbackHostApi;
11+
import java.util.Objects;
12+
13+
/**
14+
* Host API implementation for `CustomViewCallback`.
15+
*
16+
* <p>This class may handle instantiating and adding native object instances that are attached to a
17+
* Dart instance or handle method calls on the associated native class or an instance of the class.
18+
*/
19+
public class CustomViewCallbackHostApiImpl implements CustomViewCallbackHostApi {
20+
// To ease adding additional methods, this value is added prematurely.
21+
@SuppressWarnings({"unused", "FieldCanBeLocal"})
22+
private final BinaryMessenger binaryMessenger;
23+
24+
private final InstanceManager instanceManager;
25+
26+
/**
27+
* Constructs a {@link CustomViewCallbackHostApiImpl}.
28+
*
29+
* @param binaryMessenger used to communicate with Dart over asynchronous messages
30+
* @param instanceManager maintains instances stored to communicate with attached Dart objects
31+
*/
32+
public CustomViewCallbackHostApiImpl(
33+
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
34+
this.binaryMessenger = binaryMessenger;
35+
this.instanceManager = instanceManager;
36+
}
37+
38+
@Override
39+
public void onCustomViewHidden(@NonNull Long identifier) {
40+
getCustomViewCallbackInstance(identifier).onCustomViewHidden();
41+
}
42+
43+
private CustomViewCallback getCustomViewCallbackInstance(@NonNull Long identifier) {
44+
return Objects.requireNonNull(instanceManager.getInstance(identifier));
45+
}
46+
}
Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@
55
package io.flutter.plugins.webviewflutter;
66

77
import android.content.Context;
8+
import android.view.View;
89
import androidx.annotation.NonNull;
910
import androidx.annotation.Nullable;
1011
import io.flutter.plugin.common.StandardMessageCodec;
1112
import io.flutter.plugin.platform.PlatformView;
1213
import io.flutter.plugin.platform.PlatformViewFactory;
1314

14-
class FlutterWebViewFactory extends PlatformViewFactory {
15+
class FlutterViewFactory extends PlatformViewFactory {
1516
private final InstanceManager instanceManager;
1617

17-
FlutterWebViewFactory(InstanceManager instanceManager) {
18+
FlutterViewFactory(InstanceManager instanceManager) {
1819
super(StandardMessageCodec.INSTANCE);
1920
this.instanceManager = instanceManager;
2021
}
@@ -24,13 +25,26 @@ class FlutterWebViewFactory extends PlatformViewFactory {
2425
public PlatformView create(Context context, int viewId, @Nullable Object args) {
2526
final Integer identifier = (Integer) args;
2627
if (identifier == null) {
27-
throw new IllegalStateException("An identifier is required to retrieve WebView instance.");
28+
throw new IllegalStateException("An identifier is required to retrieve a View instance.");
2829
}
2930

30-
final PlatformView view = instanceManager.getInstance(identifier);
31-
if (view == null) {
32-
throw new IllegalStateException("Unable to find WebView instance: " + args);
31+
final Object instance = instanceManager.getInstance(identifier);
32+
33+
if (instance instanceof PlatformView) {
34+
return (PlatformView) instance;
35+
} else if (instance instanceof View) {
36+
return new PlatformView() {
37+
@Override
38+
public View getView() {
39+
return (View) instance;
40+
}
41+
42+
@Override
43+
public void dispose() {}
44+
};
3345
}
34-
return view;
46+
47+
throw new IllegalStateException(
48+
"Unable to find a PlatformView or View instance: " + args + ", " + instance);
3549
}
3650
}

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2602,6 +2602,33 @@ public void onPermissionRequest(
26022602
new ArrayList<Object>(Arrays.asList(instanceIdArg, requestInstanceIdArg)),
26032603
channelReply -> callback.reply(null));
26042604
}
2605+
/** Callback to Dart function `WebChromeClient.onShowCustomView`. */
2606+
public void onShowCustomView(
2607+
@NonNull Long instanceIdArg,
2608+
@NonNull Long viewIdentifierArg,
2609+
@NonNull Long callbackIdentifierArg,
2610+
@NonNull Reply<Void> callback) {
2611+
BasicMessageChannel<Object> channel =
2612+
new BasicMessageChannel<>(
2613+
binaryMessenger,
2614+
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onShowCustomView",
2615+
getCodec());
2616+
channel.send(
2617+
new ArrayList<Object>(
2618+
Arrays.asList(instanceIdArg, viewIdentifierArg, callbackIdentifierArg)),
2619+
channelReply -> callback.reply(null));
2620+
}
2621+
/** Callback to Dart function `WebChromeClient.onHideCustomView`. */
2622+
public void onHideCustomView(@NonNull Long instanceIdArg, @NonNull Reply<Void> callback) {
2623+
BasicMessageChannel<Object> channel =
2624+
new BasicMessageChannel<>(
2625+
binaryMessenger,
2626+
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onHideCustomView",
2627+
getCodec());
2628+
channel.send(
2629+
new ArrayList<Object>(Collections.singletonList(instanceIdArg)),
2630+
channelReply -> callback.reply(null));
2631+
}
26052632
/** Callback to Dart function `WebChromeClient.onGeolocationPermissionsShowPrompt`. */
26062633
public void onGeolocationPermissionsShowPrompt(
26072634
@NonNull Long instanceIdArg,
@@ -2867,6 +2894,137 @@ public void create(
28672894
channelReply -> callback.reply(null));
28682895
}
28692896
}
2897+
/**
2898+
* Host API for `CustomViewCallback`.
2899+
*
2900+
* <p>This class may handle instantiating and adding native object instances that are attached to
2901+
* a Dart instance or handle method calls on the associated native class or an instance of the
2902+
* class.
2903+
*
2904+
* <p>See
2905+
* https://developer.android.com/reference/android/webkit/WebChromeClient.CustomViewCallback.
2906+
*
2907+
* <p>Generated interface from Pigeon that represents a handler of messages from Flutter.
2908+
*/
2909+
public interface CustomViewCallbackHostApi {
2910+
/** Handles Dart method `CustomViewCallback.onCustomViewHidden`. */
2911+
void onCustomViewHidden(@NonNull Long identifier);
2912+
2913+
/** The codec used by CustomViewCallbackHostApi. */
2914+
static @NonNull MessageCodec<Object> getCodec() {
2915+
return new StandardMessageCodec();
2916+
}
2917+
/**
2918+
* Sets up an instance of `CustomViewCallbackHostApi` to handle messages through the
2919+
* `binaryMessenger`.
2920+
*/
2921+
static void setup(
2922+
@NonNull BinaryMessenger binaryMessenger, @Nullable CustomViewCallbackHostApi api) {
2923+
{
2924+
BasicMessageChannel<Object> channel =
2925+
new BasicMessageChannel<>(
2926+
binaryMessenger,
2927+
"dev.flutter.pigeon.webview_flutter_android.CustomViewCallbackHostApi.onCustomViewHidden",
2928+
getCodec());
2929+
if (api != null) {
2930+
channel.setMessageHandler(
2931+
(message, reply) -> {
2932+
ArrayList<Object> wrapped = new ArrayList<Object>();
2933+
ArrayList<Object> args = (ArrayList<Object>) message;
2934+
Number identifierArg = (Number) args.get(0);
2935+
try {
2936+
api.onCustomViewHidden(
2937+
(identifierArg == null) ? null : identifierArg.longValue());
2938+
wrapped.add(0, null);
2939+
} catch (Throwable exception) {
2940+
ArrayList<Object> wrappedError = wrapError(exception);
2941+
wrapped = wrappedError;
2942+
}
2943+
reply.reply(wrapped);
2944+
});
2945+
} else {
2946+
channel.setMessageHandler(null);
2947+
}
2948+
}
2949+
}
2950+
}
2951+
/**
2952+
* Flutter API for `CustomViewCallback`.
2953+
*
2954+
* <p>This class may handle instantiating and adding Dart instances that are attached to a native
2955+
* instance or receiving callback methods from an overridden native class.
2956+
*
2957+
* <p>See
2958+
* https://developer.android.com/reference/android/webkit/WebChromeClient.CustomViewCallback.
2959+
*
2960+
* <p>Generated class from Pigeon that represents Flutter messages that can be called from Java.
2961+
*/
2962+
public static class CustomViewCallbackFlutterApi {
2963+
private final @NonNull BinaryMessenger binaryMessenger;
2964+
2965+
public CustomViewCallbackFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
2966+
this.binaryMessenger = argBinaryMessenger;
2967+
}
2968+
2969+
/** Public interface for sending reply. */
2970+
@SuppressWarnings("UnknownNullness")
2971+
public interface Reply<T> {
2972+
void reply(T reply);
2973+
}
2974+
/** The codec used by CustomViewCallbackFlutterApi. */
2975+
static @NonNull MessageCodec<Object> getCodec() {
2976+
return new StandardMessageCodec();
2977+
}
2978+
/** Create a new Dart instance and add it to the `InstanceManager`. */
2979+
public void create(@NonNull Long identifierArg, @NonNull Reply<Void> callback) {
2980+
BasicMessageChannel<Object> channel =
2981+
new BasicMessageChannel<>(
2982+
binaryMessenger,
2983+
"dev.flutter.pigeon.webview_flutter_android.CustomViewCallbackFlutterApi.create",
2984+
getCodec());
2985+
channel.send(
2986+
new ArrayList<Object>(Collections.singletonList(identifierArg)),
2987+
channelReply -> callback.reply(null));
2988+
}
2989+
}
2990+
/**
2991+
* Flutter API for `View`.
2992+
*
2993+
* <p>This class may handle instantiating and adding Dart instances that are attached to a native
2994+
* instance or receiving callback methods from an overridden native class.
2995+
*
2996+
* <p>See https://developer.android.com/reference/android/view/View.
2997+
*
2998+
* <p>Generated class from Pigeon that represents Flutter messages that can be called from Java.
2999+
*/
3000+
public static class ViewFlutterApi {
3001+
private final @NonNull BinaryMessenger binaryMessenger;
3002+
3003+
public ViewFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
3004+
this.binaryMessenger = argBinaryMessenger;
3005+
}
3006+
3007+
/** Public interface for sending reply. */
3008+
@SuppressWarnings("UnknownNullness")
3009+
public interface Reply<T> {
3010+
void reply(T reply);
3011+
}
3012+
/** The codec used by ViewFlutterApi. */
3013+
static @NonNull MessageCodec<Object> getCodec() {
3014+
return new StandardMessageCodec();
3015+
}
3016+
/** Create a new Dart instance and add it to the `InstanceManager`. */
3017+
public void create(@NonNull Long identifierArg, @NonNull Reply<Void> callback) {
3018+
BasicMessageChannel<Object> channel =
3019+
new BasicMessageChannel<>(
3020+
binaryMessenger,
3021+
"dev.flutter.pigeon.webview_flutter_android.ViewFlutterApi.create",
3022+
getCodec());
3023+
channel.send(
3024+
new ArrayList<Object>(Collections.singletonList(identifierArg)),
3025+
channelReply -> callback.reply(null));
3026+
}
3027+
}
28703028
/**
28713029
* Host API for `GeolocationPermissionsCallback`.
28723030
*

0 commit comments

Comments
 (0)