[webview_flutter] Added 'allowsInlineMediaPlayback' property (#3334)
diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md
index 3526139..a10c28d 100644
--- a/packages/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.0.0-nullsafety.1
+
+* Added `allowsInlineMediaPlayback` property.
+
## 2.0.0-nullsafety
* Migration to null-safety.
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
index bfb79a3..ef9f006 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
@@ -372,6 +372,9 @@
case "userAgent":
updateUserAgent((String) settings.get(key));
break;
+ case "allowsInlineMediaPlayback":
+ // no-op inline media playback is always allowed on Android.
+ break;
default:
throw new IllegalArgumentException("Unknown WebView setting: " + key);
}
diff --git a/packages/webview_flutter/example/assets/sample_video.mp4 b/packages/webview_flutter/example/assets/sample_video.mp4
new file mode 100644
index 0000000..a203d0c
--- /dev/null
+++ b/packages/webview_flutter/example/assets/sample_video.mp4
Binary files differ
diff --git a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart
index 2a17c53..86cf8ab 100644
--- a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart
@@ -120,16 +120,14 @@
controllerCompleter.complete(controller);
},
javascriptMode: JavascriptMode.unrestricted,
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Echo',
onMessageReceived: (JavascriptMessage message) {
messagesReceived.add(message.message);
},
),
- ].toSet(),
+ },
onPageStarted: (String url) {
pageStarted.complete(null);
},
@@ -180,16 +178,14 @@
onWebViewCreated: (WebViewController controller) {
controllerCompleter.complete(controller);
},
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Resize',
onMessageReceived: (JavascriptMessage message) {
resizeCompleter.complete(true);
},
),
- ].toSet(),
+ },
onPageStarted: (String url) {
pageStarted.complete(null);
},
@@ -327,7 +323,218 @@
expect(customUserAgent2, defaultPlatformUserAgent);
});
- group('Media playback policy', () {
+ group('Video playback policy', () {
+ String videoTestBase64;
+ setUpAll(() async {
+ final ByteData videoData =
+ await rootBundle.load('assets/sample_video.mp4');
+ final String base64VideoData =
+ base64Encode(Uint8List.view(videoData.buffer));
+ final String videoTest = '''
+ <!DOCTYPE html><html>
+ <head><title>Video auto play</title>
+ <script type="text/javascript">
+ function play() {
+ var video = document.getElementById("video");
+ video.play();
+ }
+ function isPaused() {
+ var video = document.getElementById("video");
+ return video.paused;
+ }
+ function isFullScreen() {
+ var video = document.getElementById("video");
+ return video.webkitDisplayingFullscreen;
+ }
+ </script>
+ </head>
+ <body onload="play();">
+ <video controls playsinline autoplay id="video">
+ <source src="data:video/mp4;charset=utf-8;base64,$base64VideoData">
+ </video>
+ </body>
+ </html>
+ ''';
+ videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest));
+ });
+
+ test('Auto media playback', () async {
+ Completer<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+ Completer<void> pageLoaded = Completer<void>();
+
+ await pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: WebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (WebViewController controller) {
+ controllerCompleter.complete(controller);
+ },
+ javascriptMode: JavascriptMode.unrestricted,
+ onPageFinished: (String url) {
+ pageLoaded.complete(null);
+ },
+ initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
+ ),
+ ),
+ );
+ WebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ String isPaused = await controller.evaluateJavascript('isPaused();');
+ expect(isPaused, _webviewBool(false));
+
+ controllerCompleter = Completer<WebViewController>();
+ pageLoaded = Completer<void>();
+
+ // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy
+ await pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: WebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (WebViewController controller) {
+ controllerCompleter.complete(controller);
+ },
+ javascriptMode: JavascriptMode.unrestricted,
+ onPageFinished: (String url) {
+ pageLoaded.complete(null);
+ },
+ initialMediaPlaybackPolicy:
+ AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
+ ),
+ ),
+ );
+
+ controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ isPaused = await controller.evaluateJavascript('isPaused();');
+ expect(isPaused, _webviewBool(true));
+ });
+
+ test('Changes to initialMediaPlaybackPolicy are ignored', () async {
+ final Completer<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+ Completer<void> pageLoaded = Completer<void>();
+
+ final GlobalKey key = GlobalKey();
+ await pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: WebView(
+ key: key,
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (WebViewController controller) {
+ controllerCompleter.complete(controller);
+ },
+ javascriptMode: JavascriptMode.unrestricted,
+ onPageFinished: (String url) {
+ pageLoaded.complete(null);
+ },
+ initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
+ ),
+ ),
+ );
+ final WebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ String isPaused = await controller.evaluateJavascript('isPaused();');
+ expect(isPaused, _webviewBool(false));
+
+ pageLoaded = Completer<void>();
+
+ await pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: WebView(
+ key: key,
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (WebViewController controller) {
+ controllerCompleter.complete(controller);
+ },
+ javascriptMode: JavascriptMode.unrestricted,
+ onPageFinished: (String url) {
+ pageLoaded.complete(null);
+ },
+ initialMediaPlaybackPolicy:
+ AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
+ ),
+ ),
+ );
+
+ await controller.reload();
+
+ await pageLoaded.future;
+
+ isPaused = await controller.evaluateJavascript('isPaused();');
+ expect(isPaused, _webviewBool(false));
+ });
+
+ test('Video plays inline when allowsInlineMediaPlayback is true', () async {
+ Completer<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+ Completer<void> pageLoaded = Completer<void>();
+
+ await pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: WebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (WebViewController controller) {
+ controllerCompleter.complete(controller);
+ },
+ javascriptMode: JavascriptMode.unrestricted,
+ onPageFinished: (String url) {
+ pageLoaded.complete(null);
+ },
+ initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
+ allowsInlineMediaPlayback: true,
+ ),
+ ),
+ );
+ WebViewController controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ String isFullScreen =
+ await controller.evaluateJavascript('isFullScreen();');
+ expect(isFullScreen, _webviewBool(false));
+
+ controllerCompleter = Completer<WebViewController>();
+ pageLoaded = Completer<void>();
+
+ await pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: WebView(
+ key: GlobalKey(),
+ initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
+ onWebViewCreated: (WebViewController controller) {
+ controllerCompleter.complete(controller);
+ },
+ javascriptMode: JavascriptMode.unrestricted,
+ onPageFinished: (String url) {
+ pageLoaded.complete(null);
+ },
+ initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
+ allowsInlineMediaPlayback: false,
+ ),
+ ),
+ );
+
+ controller = await controllerCompleter.future;
+ await pageLoaded.future;
+
+ isFullScreen = await controller.evaluateJavascript('isFullScreen();');
+ expect(isFullScreen, _webviewBool(true));
+ });
+ });
+
+ group('Audio playback policy', () {
String audioTestBase64;
setUpAll(() async {
final ByteData audioData =
diff --git a/packages/webview_flutter/example/lib/main.dart b/packages/webview_flutter/example/lib/main.dart
index 7ec3008..2a4b652 100644
--- a/packages/webview_flutter/example/lib/main.dart
+++ b/packages/webview_flutter/example/lib/main.dart
@@ -62,11 +62,9 @@
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
_toasterJavascriptChannel(context),
- ].toSet(),
+ },
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
print('blocking navigation to $request}');
diff --git a/packages/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/example/pubspec.yaml
index 44c740a..543e7fd 100644
--- a/packages/webview_flutter/example/pubspec.yaml
+++ b/packages/webview_flutter/example/pubspec.yaml
@@ -23,3 +23,4 @@
uses-material-design: true
assets:
- assets/sample_audio.ogg
+ - assets/sample_video.mp4
diff --git a/packages/webview_flutter/ios/Classes/FlutterWebView.m b/packages/webview_flutter/ios/Classes/FlutterWebView.m
index 969e010..ed3cf44 100644
--- a/packages/webview_flutter/ios/Classes/FlutterWebView.m
+++ b/packages/webview_flutter/ios/Classes/FlutterWebView.m
@@ -332,6 +332,9 @@
} else if ([key isEqualToString:@"userAgent"]) {
NSString* userAgent = settings[key];
[self updateUserAgent:[userAgent isEqual:[NSNull null]] ? nil : userAgent];
+ } else if ([key isEqualToString:@"allowsInlineMediaPlayback"]) {
+ NSNumber* allowsInlineMediaPlayback = settings[key];
+ _webView.configuration.allowsInlineMediaPlayback = [allowsInlineMediaPlayback boolValue];
} else {
[unknownKeys addObject:key];
}
diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart
index f162e58..a840c00 100644
--- a/packages/webview_flutter/lib/platform_interface.dart
+++ b/packages/webview_flutter/lib/platform_interface.dart
@@ -390,6 +390,7 @@
this.hasNavigationDelegate,
this.debuggingEnabled,
this.gestureNavigationEnabled,
+ this.allowsInlineMediaPlayback,
required this.userAgent,
}) : assert(userAgent != null);
@@ -404,6 +405,11 @@
/// See also: [WebView.debuggingEnabled].
final bool? debuggingEnabled;
+ /// Whether to play HTML5 videos inline or use the native full-screen controller on iOS.
+ ///
+ /// This will have no effect on Android.
+ final bool? allowsInlineMediaPlayback;
+
/// The value used for the HTTP `User-Agent:` request header.
///
/// If [userAgent.value] is null the platform's default user agent should be used.
@@ -421,7 +427,7 @@
@override
String toString() {
- return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent)';
+ return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent, allowsInlineMediaPlayback: $allowsInlineMediaPlayback)';
}
}
diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart
index 1c666d7..b38d65a 100644
--- a/packages/webview_flutter/lib/src/webview_method_channel.dart
+++ b/packages/webview_flutter/lib/src/webview_method_channel.dart
@@ -185,6 +185,8 @@
_addIfNonNull('debuggingEnabled', settings.debuggingEnabled);
_addIfNonNull(
'gestureNavigationEnabled', settings.gestureNavigationEnabled);
+ _addIfNonNull(
+ 'allowsInlineMediaPlayback', settings.allowsInlineMediaPlayback);
_addSettingIfPresent('userAgent', settings.userAgent);
return map;
}
diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart
index 4327c78..6853d39 100644
--- a/packages/webview_flutter/lib/webview_flutter.dart
+++ b/packages/webview_flutter/lib/webview_flutter.dart
@@ -223,8 +223,10 @@
this.userAgent,
this.initialMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
+ this.allowsInlineMediaPlayback = false,
}) : assert(javascriptMode != null),
assert(initialMediaPlaybackPolicy != null),
+ assert(allowsInlineMediaPlayback != null),
super(key: key);
static WebViewPlatform? _platform;
@@ -333,6 +335,13 @@
/// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header.
final NavigationDelegate? navigationDelegate;
+ /// Controls whether inline playback of HTML5 videos is allowed on iOS.
+ ///
+ /// This field is ignored on Android because Android allows it by default.
+ ///
+ /// By default `allowsInlineMediaPlayback` is false.
+ final bool allowsInlineMediaPlayback;
+
/// Invoked when a page starts loading.
final PageStartedCallback? onPageStarted;
@@ -469,6 +478,7 @@
hasNavigationDelegate: widget.navigationDelegate != null,
debuggingEnabled: widget.debuggingEnabled,
gestureNavigationEnabled: widget.gestureNavigationEnabled,
+ allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback,
userAgent: WebSetting<String?>.of(widget.userAgent),
);
}
diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml
index de62a50..75910df 100644
--- a/packages/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/pubspec.yaml
@@ -1,6 +1,6 @@
name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
-version: 2.0.0-nullsafety
+version: 2.0.0-nullsafety.1
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
environment:
diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart
index 662a2f7..162b193 100644
--- a/packages/webview_flutter/test/webview_flutter_test.dart
+++ b/packages/webview_flutter/test/webview_flutter_test.dart
@@ -423,14 +423,12 @@
await tester.pumpWidget(
WebView(
initialUrl: 'https://youtube.com',
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}),
JavascriptChannel(
name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}),
- ].toSet(),
+ },
),
);
@@ -463,14 +461,12 @@
await tester.pumpWidget(
WebView(
initialUrl: 'https://youtube.com',
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}),
JavascriptChannel(
name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}),
- ].toSet(),
+ },
),
);
expect(tester.takeException(), isNot(null));
@@ -480,30 +476,26 @@
await tester.pumpWidget(
WebView(
initialUrl: 'https://youtube.com',
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}),
JavascriptChannel(
name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}),
- ].toSet(),
+ },
),
);
await tester.pumpWidget(
WebView(
initialUrl: 'https://youtube.com',
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}),
JavascriptChannel(
name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}),
JavascriptChannel(
name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}),
- ].toSet(),
+ },
),
);
@@ -523,12 +515,10 @@
await tester.pumpWidget(
WebView(
initialUrl: 'https://youtube.com',
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}),
- ].toSet(),
+ },
),
);
@@ -541,12 +531,10 @@
await tester.pumpWidget(
WebView(
initialUrl: 'https://youtube.com',
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}),
- ].toSet(),
+ },
),
);
@@ -563,9 +551,7 @@
await tester.pumpWidget(
WebView(
initialUrl: 'https://youtube.com',
- // TODO(iskakaushik): Remove this when collection literals makes it to stable.
- // ignore: prefer_collection_literals
- javascriptChannels: <JavascriptChannel>[
+ javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Tts',
onMessageReceived: (JavascriptMessage msg) {
@@ -576,7 +562,7 @@
onMessageReceived: (JavascriptMessage msg) {
alarmMessagesReceived.add(msg.message);
}),
- ].toSet(),
+ },
),
);