Skip to content

Commit aa8fcb4

Browse files
authored
[webview_flutter] Added 'allowsInlineMediaPlayback' property (flutter#3334)
1 parent b85d8eb commit aa8fcb4

File tree

12 files changed

+263
-43
lines changed

12 files changed

+263
-43
lines changed

packages/webview_flutter/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.0.0-nullsafety.1
2+
3+
* Added `allowsInlineMediaPlayback` property.
4+
15
## 2.0.0-nullsafety
26

37
* Migration to null-safety.

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

+3
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,9 @@ private void applySettings(Map<String, Object> settings) {
372372
case "userAgent":
373373
updateUserAgent((String) settings.get(key));
374374
break;
375+
case "allowsInlineMediaPlayback":
376+
// no-op inline media playback is always allowed on Android.
377+
break;
375378
default:
376379
throw new IllegalArgumentException("Unknown WebView setting: " + key);
377380
}
Binary file not shown.

packages/webview_flutter/example/integration_test/webview_flutter_test.dart

+216-9
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,14 @@ void main() {
120120
controllerCompleter.complete(controller);
121121
},
122122
javascriptMode: JavascriptMode.unrestricted,
123-
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
124-
// ignore: prefer_collection_literals
125-
javascriptChannels: <JavascriptChannel>[
123+
javascriptChannels: <JavascriptChannel>{
126124
JavascriptChannel(
127125
name: 'Echo',
128126
onMessageReceived: (JavascriptMessage message) {
129127
messagesReceived.add(message.message);
130128
},
131129
),
132-
].toSet(),
130+
},
133131
onPageStarted: (String url) {
134132
pageStarted.complete(null);
135133
},
@@ -180,16 +178,14 @@ void main() {
180178
onWebViewCreated: (WebViewController controller) {
181179
controllerCompleter.complete(controller);
182180
},
183-
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
184-
// ignore: prefer_collection_literals
185-
javascriptChannels: <JavascriptChannel>[
181+
javascriptChannels: <JavascriptChannel>{
186182
JavascriptChannel(
187183
name: 'Resize',
188184
onMessageReceived: (JavascriptMessage message) {
189185
resizeCompleter.complete(true);
190186
},
191187
),
192-
].toSet(),
188+
},
193189
onPageStarted: (String url) {
194190
pageStarted.complete(null);
195191
},
@@ -327,7 +323,218 @@ void main() {
327323
expect(customUserAgent2, defaultPlatformUserAgent);
328324
});
329325

330-
group('Media playback policy', () {
326+
group('Video playback policy', () {
327+
String videoTestBase64;
328+
setUpAll(() async {
329+
final ByteData videoData =
330+
await rootBundle.load('assets/sample_video.mp4');
331+
final String base64VideoData =
332+
base64Encode(Uint8List.view(videoData.buffer));
333+
final String videoTest = '''
334+
<!DOCTYPE html><html>
335+
<head><title>Video auto play</title>
336+
<script type="text/javascript">
337+
function play() {
338+
var video = document.getElementById("video");
339+
video.play();
340+
}
341+
function isPaused() {
342+
var video = document.getElementById("video");
343+
return video.paused;
344+
}
345+
function isFullScreen() {
346+
var video = document.getElementById("video");
347+
return video.webkitDisplayingFullscreen;
348+
}
349+
</script>
350+
</head>
351+
<body onload="play();">
352+
<video controls playsinline autoplay id="video">
353+
<source src="data:video/mp4;charset=utf-8;base64,$base64VideoData">
354+
</video>
355+
</body>
356+
</html>
357+
''';
358+
videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest));
359+
});
360+
361+
test('Auto media playback', () async {
362+
Completer<WebViewController> controllerCompleter =
363+
Completer<WebViewController>();
364+
Completer<void> pageLoaded = Completer<void>();
365+
366+
await pumpWidget(
367+
Directionality(
368+
textDirection: TextDirection.ltr,
369+
child: WebView(
370+
key: GlobalKey(),
371+
initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
372+
onWebViewCreated: (WebViewController controller) {
373+
controllerCompleter.complete(controller);
374+
},
375+
javascriptMode: JavascriptMode.unrestricted,
376+
onPageFinished: (String url) {
377+
pageLoaded.complete(null);
378+
},
379+
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
380+
),
381+
),
382+
);
383+
WebViewController controller = await controllerCompleter.future;
384+
await pageLoaded.future;
385+
386+
String isPaused = await controller.evaluateJavascript('isPaused();');
387+
expect(isPaused, _webviewBool(false));
388+
389+
controllerCompleter = Completer<WebViewController>();
390+
pageLoaded = Completer<void>();
391+
392+
// We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy
393+
await pumpWidget(
394+
Directionality(
395+
textDirection: TextDirection.ltr,
396+
child: WebView(
397+
key: GlobalKey(),
398+
initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
399+
onWebViewCreated: (WebViewController controller) {
400+
controllerCompleter.complete(controller);
401+
},
402+
javascriptMode: JavascriptMode.unrestricted,
403+
onPageFinished: (String url) {
404+
pageLoaded.complete(null);
405+
},
406+
initialMediaPlaybackPolicy:
407+
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
408+
),
409+
),
410+
);
411+
412+
controller = await controllerCompleter.future;
413+
await pageLoaded.future;
414+
415+
isPaused = await controller.evaluateJavascript('isPaused();');
416+
expect(isPaused, _webviewBool(true));
417+
});
418+
419+
test('Changes to initialMediaPlaybackPolicy are ignored', () async {
420+
final Completer<WebViewController> controllerCompleter =
421+
Completer<WebViewController>();
422+
Completer<void> pageLoaded = Completer<void>();
423+
424+
final GlobalKey key = GlobalKey();
425+
await pumpWidget(
426+
Directionality(
427+
textDirection: TextDirection.ltr,
428+
child: WebView(
429+
key: key,
430+
initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
431+
onWebViewCreated: (WebViewController controller) {
432+
controllerCompleter.complete(controller);
433+
},
434+
javascriptMode: JavascriptMode.unrestricted,
435+
onPageFinished: (String url) {
436+
pageLoaded.complete(null);
437+
},
438+
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
439+
),
440+
),
441+
);
442+
final WebViewController controller = await controllerCompleter.future;
443+
await pageLoaded.future;
444+
445+
String isPaused = await controller.evaluateJavascript('isPaused();');
446+
expect(isPaused, _webviewBool(false));
447+
448+
pageLoaded = Completer<void>();
449+
450+
await pumpWidget(
451+
Directionality(
452+
textDirection: TextDirection.ltr,
453+
child: WebView(
454+
key: key,
455+
initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
456+
onWebViewCreated: (WebViewController controller) {
457+
controllerCompleter.complete(controller);
458+
},
459+
javascriptMode: JavascriptMode.unrestricted,
460+
onPageFinished: (String url) {
461+
pageLoaded.complete(null);
462+
},
463+
initialMediaPlaybackPolicy:
464+
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
465+
),
466+
),
467+
);
468+
469+
await controller.reload();
470+
471+
await pageLoaded.future;
472+
473+
isPaused = await controller.evaluateJavascript('isPaused();');
474+
expect(isPaused, _webviewBool(false));
475+
});
476+
477+
test('Video plays inline when allowsInlineMediaPlayback is true', () async {
478+
Completer<WebViewController> controllerCompleter =
479+
Completer<WebViewController>();
480+
Completer<void> pageLoaded = Completer<void>();
481+
482+
await pumpWidget(
483+
Directionality(
484+
textDirection: TextDirection.ltr,
485+
child: WebView(
486+
key: GlobalKey(),
487+
initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
488+
onWebViewCreated: (WebViewController controller) {
489+
controllerCompleter.complete(controller);
490+
},
491+
javascriptMode: JavascriptMode.unrestricted,
492+
onPageFinished: (String url) {
493+
pageLoaded.complete(null);
494+
},
495+
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
496+
allowsInlineMediaPlayback: true,
497+
),
498+
),
499+
);
500+
WebViewController controller = await controllerCompleter.future;
501+
await pageLoaded.future;
502+
503+
String isFullScreen =
504+
await controller.evaluateJavascript('isFullScreen();');
505+
expect(isFullScreen, _webviewBool(false));
506+
507+
controllerCompleter = Completer<WebViewController>();
508+
pageLoaded = Completer<void>();
509+
510+
await pumpWidget(
511+
Directionality(
512+
textDirection: TextDirection.ltr,
513+
child: WebView(
514+
key: GlobalKey(),
515+
initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64',
516+
onWebViewCreated: (WebViewController controller) {
517+
controllerCompleter.complete(controller);
518+
},
519+
javascriptMode: JavascriptMode.unrestricted,
520+
onPageFinished: (String url) {
521+
pageLoaded.complete(null);
522+
},
523+
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
524+
allowsInlineMediaPlayback: false,
525+
),
526+
),
527+
);
528+
529+
controller = await controllerCompleter.future;
530+
await pageLoaded.future;
531+
532+
isFullScreen = await controller.evaluateJavascript('isFullScreen();');
533+
expect(isFullScreen, _webviewBool(true));
534+
});
535+
});
536+
537+
group('Audio playback policy', () {
331538
String audioTestBase64;
332539
setUpAll(() async {
333540
final ByteData audioData =

packages/webview_flutter/example/lib/main.dart

+2-4
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,9 @@ class _WebViewExampleState extends State<WebViewExample> {
6262
onWebViewCreated: (WebViewController webViewController) {
6363
_controller.complete(webViewController);
6464
},
65-
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
66-
// ignore: prefer_collection_literals
67-
javascriptChannels: <JavascriptChannel>[
65+
javascriptChannels: <JavascriptChannel>{
6866
_toasterJavascriptChannel(context),
69-
].toSet(),
67+
},
7068
navigationDelegate: (NavigationRequest request) {
7169
if (request.url.startsWith('https://www.youtube.com/')) {
7270
print('blocking navigation to $request}');

packages/webview_flutter/example/pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ flutter:
2323
uses-material-design: true
2424
assets:
2525
- assets/sample_audio.ogg
26+
- assets/sample_video.mp4

packages/webview_flutter/ios/Classes/FlutterWebView.m

+3
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ - (NSString*)applySettings:(NSDictionary<NSString*, id>*)settings {
332332
} else if ([key isEqualToString:@"userAgent"]) {
333333
NSString* userAgent = settings[key];
334334
[self updateUserAgent:[userAgent isEqual:[NSNull null]] ? nil : userAgent];
335+
} else if ([key isEqualToString:@"allowsInlineMediaPlayback"]) {
336+
NSNumber* allowsInlineMediaPlayback = settings[key];
337+
_webView.configuration.allowsInlineMediaPlayback = [allowsInlineMediaPlayback boolValue];
335338
} else {
336339
[unknownKeys addObject:key];
337340
}

packages/webview_flutter/lib/platform_interface.dart

+7-1
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ class WebSettings {
390390
this.hasNavigationDelegate,
391391
this.debuggingEnabled,
392392
this.gestureNavigationEnabled,
393+
this.allowsInlineMediaPlayback,
393394
required this.userAgent,
394395
}) : assert(userAgent != null);
395396

@@ -404,6 +405,11 @@ class WebSettings {
404405
/// See also: [WebView.debuggingEnabled].
405406
final bool? debuggingEnabled;
406407

408+
/// Whether to play HTML5 videos inline or use the native full-screen controller on iOS.
409+
///
410+
/// This will have no effect on Android.
411+
final bool? allowsInlineMediaPlayback;
412+
407413
/// The value used for the HTTP `User-Agent:` request header.
408414
///
409415
/// If [userAgent.value] is null the platform's default user agent should be used.
@@ -421,7 +427,7 @@ class WebSettings {
421427

422428
@override
423429
String toString() {
424-
return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent)';
430+
return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent, allowsInlineMediaPlayback: $allowsInlineMediaPlayback)';
425431
}
426432
}
427433

packages/webview_flutter/lib/src/webview_method_channel.dart

+2
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
185185
_addIfNonNull('debuggingEnabled', settings.debuggingEnabled);
186186
_addIfNonNull(
187187
'gestureNavigationEnabled', settings.gestureNavigationEnabled);
188+
_addIfNonNull(
189+
'allowsInlineMediaPlayback', settings.allowsInlineMediaPlayback);
188190
_addSettingIfPresent('userAgent', settings.userAgent);
189191
return map;
190192
}

packages/webview_flutter/lib/webview_flutter.dart

+10
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,10 @@ class WebView extends StatefulWidget {
223223
this.userAgent,
224224
this.initialMediaPlaybackPolicy =
225225
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
226+
this.allowsInlineMediaPlayback = false,
226227
}) : assert(javascriptMode != null),
227228
assert(initialMediaPlaybackPolicy != null),
229+
assert(allowsInlineMediaPlayback != null),
228230
super(key: key);
229231

230232
static WebViewPlatform? _platform;
@@ -333,6 +335,13 @@ class WebView extends StatefulWidget {
333335
/// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header.
334336
final NavigationDelegate? navigationDelegate;
335337

338+
/// Controls whether inline playback of HTML5 videos is allowed on iOS.
339+
///
340+
/// This field is ignored on Android because Android allows it by default.
341+
///
342+
/// By default `allowsInlineMediaPlayback` is false.
343+
final bool allowsInlineMediaPlayback;
344+
336345
/// Invoked when a page starts loading.
337346
final PageStartedCallback? onPageStarted;
338347

@@ -469,6 +478,7 @@ WebSettings _webSettingsFromWidget(WebView widget) {
469478
hasNavigationDelegate: widget.navigationDelegate != null,
470479
debuggingEnabled: widget.debuggingEnabled,
471480
gestureNavigationEnabled: widget.gestureNavigationEnabled,
481+
allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback,
472482
userAgent: WebSetting<String?>.of(widget.userAgent),
473483
);
474484
}

packages/webview_flutter/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: webview_flutter
22
description: A Flutter plugin that provides a WebView widget on Android and iOS.
3-
version: 2.0.0-nullsafety
3+
version: 2.0.0-nullsafety.1
44
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
55

66
environment:

0 commit comments

Comments
 (0)