Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 1d7e5eb

Browse files
committed
[webview_flutter] Added 'allowsInlineMediaPlayback' property
1 parent bad9fd1 commit 1d7e5eb

File tree

11 files changed

+266
-43
lines changed

11 files changed

+266
-43
lines changed

packages/webview_flutter/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ public void onMethodCall(MethodCall methodCall, Result result) {
222222
case "getScrollY":
223223
getScrollY(result);
224224
break;
225+
case "allowsInlineMediaPlayback":
226+
// no-op inline media playback is always allowed on Android.
227+
break;
225228
default:
226229
result.notImplemented();
227230
}

packages/webview_flutter/example/integration_test/webview_flutter_test.dart

Lines changed: 220 additions & 9 deletions
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,222 @@ 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+
if (Platform.isIOS) {
479+
Completer<WebViewController> controllerCompleter =
480+
Completer<WebViewController>();
481+
Completer<void> pageLoaded = Completer<void>();
482+
483+
await pumpWidget(
484+
Directionality(
485+
textDirection: TextDirection.ltr,
486+
child: WebView(
487+
key: GlobalKey(),
488+
initialUrl:
489+
'data:text/html;charset=utf-8;base64,$videoTestBase64',
490+
onWebViewCreated: (WebViewController controller) {
491+
controllerCompleter.complete(controller);
492+
},
493+
javascriptMode: JavascriptMode.unrestricted,
494+
onPageFinished: (String url) {
495+
pageLoaded.complete(null);
496+
},
497+
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
498+
allowsInlineMediaPlayback: true,
499+
),
500+
),
501+
);
502+
WebViewController controller = await controllerCompleter.future;
503+
await pageLoaded.future;
504+
505+
String isFullScreen =
506+
await controller.evaluateJavascript('isFullScreen();');
507+
expect(isFullScreen, _webviewBool(false));
508+
509+
controllerCompleter = Completer<WebViewController>();
510+
pageLoaded = Completer<void>();
511+
512+
await pumpWidget(
513+
Directionality(
514+
textDirection: TextDirection.ltr,
515+
child: WebView(
516+
key: GlobalKey(),
517+
initialUrl:
518+
'data:text/html;charset=utf-8;base64,$videoTestBase64',
519+
onWebViewCreated: (WebViewController controller) {
520+
controllerCompleter.complete(controller);
521+
},
522+
javascriptMode: JavascriptMode.unrestricted,
523+
onPageFinished: (String url) {
524+
pageLoaded.complete(null);
525+
},
526+
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
527+
allowsInlineMediaPlayback: false,
528+
),
529+
),
530+
);
531+
532+
controller = await controllerCompleter.future;
533+
await pageLoaded.future;
534+
535+
isFullScreen = await controller.evaluateJavascript('isFullScreen();');
536+
expect(isFullScreen, _webviewBool(true));
537+
}
538+
});
539+
});
540+
541+
group('Audio playback policy', () {
331542
String audioTestBase64;
332543
setUpAll(() async {
333544
final ByteData audioData =

packages/webview_flutter/example/lib/main.dart

Lines changed: 2 additions & 4 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 7 additions & 1 deletion
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ 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),
228229
super(key: key);
@@ -333,6 +334,13 @@ class WebView extends StatefulWidget {
333334
/// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header.
334335
final NavigationDelegate? navigationDelegate;
335336

337+
/// Controls whether inline playback of HTML5 videos is allowed on iOS.
338+
///
339+
/// This field is ignored on Android.
340+
///
341+
/// By default `allowsInlineMediaPlayback` is false.
342+
final bool? allowsInlineMediaPlayback;
343+
336344
/// Invoked when a page starts loading.
337345
final PageStartedCallback? onPageStarted;
338346

@@ -469,6 +477,7 @@ WebSettings _webSettingsFromWidget(WebView widget) {
469477
hasNavigationDelegate: widget.navigationDelegate != null,
470478
debuggingEnabled: widget.debuggingEnabled,
471479
gestureNavigationEnabled: widget.gestureNavigationEnabled,
480+
allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback,
472481
userAgent: WebSetting<String?>.of(widget.userAgent),
473482
);
474483
}

packages/webview_flutter/pubspec.yaml

Lines changed: 1 addition & 1 deletion
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)