Skip to content

Commit

Permalink
[webview_flutter] Added 'allowsInlineMediaPlayback' property (flutter…
Browse files Browse the repository at this point in the history
  • Loading branch information
mehmetf authored Dec 16, 2020
1 parent b85d8eb commit aa8fcb4
Show file tree
Hide file tree
Showing 12 changed files with 263 additions and 43 deletions.
4 changes: 4 additions & 0 deletions packages/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.0.0-nullsafety.1

* Added `allowsInlineMediaPlayback` property.

## 2.0.0-nullsafety

* Migration to null-safety.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,9 @@ private void applySettings(Map<String, Object> settings) {
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);
}
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,14 @@ void main() {
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);
},
Expand Down Expand Up @@ -180,16 +178,14 @@ void main() {
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);
},
Expand Down Expand Up @@ -327,7 +323,218 @@ void main() {
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 =
Expand Down
6 changes: 2 additions & 4 deletions packages/webview_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,9 @@ class _WebViewExampleState extends State<WebViewExample> {
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}');
Expand Down
1 change: 1 addition & 0 deletions packages/webview_flutter/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ flutter:
uses-material-design: true
assets:
- assets/sample_audio.ogg
- assets/sample_video.mp4
3 changes: 3 additions & 0 deletions packages/webview_flutter/ios/Classes/FlutterWebView.m
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,9 @@ - (NSString*)applySettings:(NSDictionary<NSString*, id>*)settings {
} 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];
}
Expand Down
8 changes: 7 additions & 1 deletion packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ class WebSettings {
this.hasNavigationDelegate,
this.debuggingEnabled,
this.gestureNavigationEnabled,
this.allowsInlineMediaPlayback,
required this.userAgent,
}) : assert(userAgent != null);

Expand All @@ -404,6 +405,11 @@ class WebSettings {
/// 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.
Expand All @@ -421,7 +427,7 @@ class WebSettings {

@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)';
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/webview_flutter/lib/src/webview_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
_addIfNonNull('debuggingEnabled', settings.debuggingEnabled);
_addIfNonNull(
'gestureNavigationEnabled', settings.gestureNavigationEnabled);
_addIfNonNull(
'allowsInlineMediaPlayback', settings.allowsInlineMediaPlayback);
_addSettingIfPresent('userAgent', settings.userAgent);
return map;
}
Expand Down
10 changes: 10 additions & 0 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,10 @@ class WebView extends StatefulWidget {
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;
Expand Down Expand Up @@ -333,6 +335,13 @@ class WebView extends StatefulWidget {
/// * 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;

Expand Down Expand Up @@ -469,6 +478,7 @@ WebSettings _webSettingsFromWidget(WebView widget) {
hasNavigationDelegate: widget.navigationDelegate != null,
debuggingEnabled: widget.debuggingEnabled,
gestureNavigationEnabled: widget.gestureNavigationEnabled,
allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback,
userAgent: WebSetting<String?>.of(widget.userAgent),
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
Loading

0 comments on commit aa8fcb4

Please sign in to comment.