diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index 313b29cda9fb..aeeaf6a1a6ca 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.15+2 + +* Added `allowsInlineMediaPlayback` property. + ## 0.3.15+1 * Revert the prior embedding support add since it requires an API that hasn't 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 a7f2db308e15..3dc3ce158fd2 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 @@ -252,6 +252,9 @@ private void applySettings(Map settings) { webView.setWebContentsDebuggingEnabled(debuggingEnabled); break; + case "allowsInlineMediaPlayback": + // no-op inline media playback is always allowed on Android. + break; case "userAgent": updateUserAgent((String) settings.get(key)); break; diff --git a/packages/webview_flutter/example/assets/sample_audio.ogg b/packages/webview_flutter/example/assets/sample_audio.ogg deleted file mode 100644 index 27e17104277b..000000000000 Binary files a/packages/webview_flutter/example/assets/sample_audio.ogg and /dev/null differ 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 000000000000..a203d0cdf13e Binary files /dev/null and b/packages/webview_flutter/example/assets/sample_video.mp4 differ diff --git a/packages/webview_flutter/example/lib/main.dart b/packages/webview_flutter/example/lib/main.dart index 5f3e0f8ff4fa..9e6f6641ed66 100644 --- a/packages/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/example/lib/main.dart @@ -53,11 +53,9 @@ class _WebViewExampleState extends State { onWebViewCreated: (WebViewController webViewController) { _controller.complete(webViewController); }, - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { _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 3a73afaffa44..96fdc07102c6 100644 --- a/packages/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/example/pubspec.yaml @@ -1,10 +1,10 @@ name: webview_flutter_example description: Demonstrates how to use the webview_flutter plugin. -version: 1.0.3 +version: 1.0.4 environment: - sdk: ">=2.0.0-dev.68.0 <3.0.0" + sdk: ">=2.2.0 <3.0.0" dependencies: flutter: @@ -21,4 +21,4 @@ dev_dependencies: flutter: uses-material-design: true assets: - - assets/sample_audio.ogg + - assets/sample_video.mp4 diff --git a/packages/webview_flutter/example/test_driver/webview.dart b/packages/webview_flutter/example/test_driver/webview.dart index e24afd73f557..03369f7f33f8 100644 --- a/packages/webview_flutter/example/test_driver/webview.dart +++ b/packages/webview_flutter/example/test_driver/webview.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; @@ -113,16 +114,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: [ + javascriptChannels: { JavascriptChannel( name: 'Echo', onMessageReceived: (JavascriptMessage message) { messagesReceived.add(message.message); }, ), - ].toSet(), + }, onPageFinished: (String url) { pageLoaded.complete(null); }, @@ -168,16 +167,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: [ + javascriptChannels: { JavascriptChannel( name: 'Resize', onMessageReceived: (JavascriptMessage message) { resizeCompleter.complete(true); }, ), - ].toSet(), + }, onPageFinished: (String url) { pageLoaded.complete(null); }, @@ -311,34 +308,38 @@ void main() { }); group('Media playback policy', () { - String audioTestBase64; + String videoTestBase64; setUpAll(() async { - final ByteData audioData = - await rootBundle.load('assets/sample_audio.ogg'); - final String base64AudioData = - base64Encode(Uint8List.view(audioData.buffer)); - final String audioTest = ''' + final ByteData videoData = + await rootBundle.load('assets/sample_video.mp4'); + final String base64VideoData = + base64Encode(Uint8List.view(videoData.buffer)); + final String videoTest = ''' - Audio auto play + Video auto play - + '''; - audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); + videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); }); test('Auto media playback', () async { @@ -351,7 +352,7 @@ void main() { textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, @@ -378,7 +379,7 @@ void main() { textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, @@ -399,7 +400,7 @@ void main() { expect(isPaused, _webviewBool(true)); }); - test('Changes to initialMediaPlaybackPolocy are ignored', () async { + test('Changes to initialMediaPlaybackPolicy are ignored', () async { final Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); @@ -410,7 +411,7 @@ void main() { textDirection: TextDirection.ltr, child: WebView( key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, @@ -435,7 +436,7 @@ void main() { textDirection: TextDirection.ltr, child: WebView( key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, @@ -456,6 +457,69 @@ void main() { isPaused = await controller.evaluateJavascript('isPaused();'); expect(isPaused, _webviewBool(false)); }); + + test('Video plays inline when allowsInlineMediaPlayback is true', () async { + if (Platform.isIOS) { + Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + 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(); + pageLoaded = Completer(); + + 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)); + } + }); }); test('getTitle', () async { diff --git a/packages/webview_flutter/ios/Classes/FlutterWebView.m b/packages/webview_flutter/ios/Classes/FlutterWebView.m index 60fa24052038..1a42f1ee10e6 100644 --- a/packages/webview_flutter/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/ios/Classes/FlutterWebView.m @@ -260,6 +260,9 @@ - (NSString*)applySettings:(NSDictionary*)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]; } diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 7e82bae91138..aa93281f1a4d 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -224,6 +224,7 @@ class WebSettings { this.javascriptMode, this.hasNavigationDelegate, this.debuggingEnabled, + this.allowsInlineMediaPlayback, @required this.userAgent, }) : assert(userAgent != null); @@ -238,6 +239,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. @@ -250,7 +256,7 @@ class WebSettings { @override String toString() { - return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, userAgent: $userAgent,)'; + return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, 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 c2949cc77a2a..ae36d0ba8fbb 100644 --- a/packages/webview_flutter/lib/src/webview_method_channel.dart +++ b/packages/webview_flutter/lib/src/webview_method_channel.dart @@ -132,6 +132,8 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { _addIfNonNull('jsMode', settings.javascriptMode?.index); _addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate); _addIfNonNull('debuggingEnabled', settings.debuggingEnabled); + _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 cd5ca46701d7..98da0d3388b6 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -143,6 +143,7 @@ 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), super(key: key); @@ -300,6 +301,13 @@ class WebView extends StatefulWidget { /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; + /// Controls whether inline playback of HTML5 videos is allowed on iOS. + /// + /// This field is ignored on Android. + /// + /// By default `allowsInlineMediaPlayback` is false. + final bool allowsInlineMediaPlayback; + @override State createState() => _WebViewState(); } @@ -372,6 +380,7 @@ WebSettings _webSettingsFromWidget(WebView widget) { javascriptMode: widget.javascriptMode, hasNavigationDelegate: widget.navigationDelegate != null, debuggingEnabled: widget.debuggingEnabled, + allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, userAgent: WebSetting.of(widget.userAgent), ); } @@ -415,9 +424,7 @@ WebSettings _clearUnchangedWebSettings( Set _extractChannelNames(Set channels) { final Set channelNames = channels == null - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - ? Set() + ? {} : channels.map((JavascriptChannel channel) => channel.name).toSet(); return channelNames; } diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index cd8bfb5d4044..4219cd809de8 100644 --- a/packages/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/pubspec.yaml @@ -1,11 +1,11 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. -version: 0.3.15+1 +version: 0.3.15+2 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter environment: - sdk: ">=2.0.0-dev.68.0 <3.0.0" + sdk: ">=2.2.0 <3.0.0" flutter: ">=1.5.0 <2.0.0" dependencies: diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart index 5184ddd13de3..5012913e3663 100644 --- a/packages/webview_flutter/test/webview_flutter_test.dart +++ b/packages/webview_flutter/test/webview_flutter_test.dart @@ -432,14 +432,12 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); @@ -472,14 +470,12 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); expect(tester.takeException(), isNot(null)); @@ -489,30 +485,26 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { 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: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); @@ -532,12 +524,10 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); @@ -550,12 +540,10 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); @@ -572,9 +560,7 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) { @@ -585,7 +571,7 @@ void main() { onMessageReceived: (JavascriptMessage msg) { alarmMessagesReceived.add(msg.message); }), - ].toSet(), + }, ), ); @@ -781,9 +767,7 @@ void main() { debuggingEnabled: false, userAgent: WebSetting.of(null), ), - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannelNames: Set(), + javascriptChannelNames: {}, ))); });