From e490ed6a6857e4e2b1174704e62b9fe0255eea97 Mon Sep 17 00:00:00 2001 From: James Leahy Date: Wed, 22 Feb 2023 17:14:01 +0100 Subject: [PATCH 01/12] feat: video_player web options --- .../lib/src/video_player.dart | 19 +++++++++++++++++++ .../lib/video_player_web.dart | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index bc0021dee34b..a4a53bcc04ea 100644 --- a/packages/video_player/video_player_web/lib/src/video_player.dart +++ b/packages/video_player/video_player_web/lib/src/video_player.dart @@ -45,6 +45,7 @@ class VideoPlayer { final StreamController _eventController; final html.VideoElement _videoElement; + void Function(html.Event)? _onContextMenu; bool _isInitialized = false; bool _isBuffering = false; @@ -202,9 +203,27 @@ class VideoPlayer { return Duration(milliseconds: (_videoElement.currentTime * 1000).round()); } + /// Sets options + Future setOptions(VideoPlayerWebOptions options) async { + if (options.controlsEnabled) { + _videoElement.controls = true; + final String controlsList = options.controlsList; + if (controlsList.isNotEmpty) { + _videoElement.setAttribute('controlsList', controlsList); + } + } + + if (!options.allowContextMenu) { + _onContextMenu = (html.Event event) => event.preventDefault(); + _videoElement.addEventListener('contextmenu', _onContextMenu); + } + } + /// Disposes of the current [html.VideoElement]. void dispose() { _videoElement.removeAttribute('src'); + _videoElement.removeEventListener('contextmenu', _onContextMenu); + _onContextMenu = null; _videoElement.load(); } diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index e52fd83de79e..aa5937eb0591 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -132,6 +132,11 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { return _player(textureId).events; } + @override + Future setWebOptions(int textureId, VideoPlayerWebOptions options) { + return _player(textureId).setOptions(options); + } + // Retrieves a [VideoPlayer] by its internal `id`. // It must have been created earlier from the [create] method. VideoPlayer _player(int id) { From 160f0dcc843a6d56c39d0ae3194ec3de3a797ae8 Mon Sep 17 00:00:00 2001 From: James Leahy Date: Wed, 17 May 2023 11:42:54 +0200 Subject: [PATCH 02/12] feat: split controls into VideoPlayerWebOptionsControls --- .../video_player/video_player_web/lib/src/video_player.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index a4a53bcc04ea..6fddd176a0f3 100644 --- a/packages/video_player/video_player_web/lib/src/video_player.dart +++ b/packages/video_player/video_player_web/lib/src/video_player.dart @@ -205,9 +205,9 @@ class VideoPlayer { /// Sets options Future setOptions(VideoPlayerWebOptions options) async { - if (options.controlsEnabled) { + if (options.controls.enabled) { _videoElement.controls = true; - final String controlsList = options.controlsList; + final String controlsList = options.controls.controlsList; if (controlsList.isNotEmpty) { _videoElement.setAttribute('controlsList', controlsList); } From d24aedc78c69c0695464ccf75a3faf388fdf1911 Mon Sep 17 00:00:00 2001 From: James Leahy Date: Thu, 6 Jul 2023 17:00:29 +0200 Subject: [PATCH 03/12] feat: Add allowPictureInPicture (controls), allowRemotePlayback --- .../video_player_web/lib/src/video_player.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index 6fddd176a0f3..343c136859e2 100644 --- a/packages/video_player/video_player_web/lib/src/video_player.dart +++ b/packages/video_player/video_player_web/lib/src/video_player.dart @@ -211,12 +211,20 @@ class VideoPlayer { if (controlsList.isNotEmpty) { _videoElement.setAttribute('controlsList', controlsList); } + + if (!options.controls.allowPictureInPicture) { + _videoElement.setAttribute('disablePictureInPicture', true); + } } if (!options.allowContextMenu) { _onContextMenu = (html.Event event) => event.preventDefault(); _videoElement.addEventListener('contextmenu', _onContextMenu); } + + if (!options.allowRemotePlayback) { + _videoElement.setAttribute('disableRemotePlayback', true); + } } /// Disposes of the current [html.VideoElement]. From 5da71161da67971fca4b5119b91617c64a051b87 Mon Sep 17 00:00:00 2001 From: James Leahy Date: Sat, 8 Jul 2023 09:55:27 +0200 Subject: [PATCH 04/12] chore: Update CHANGELOG --- packages/video_player/video_player_web/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index 2f182aaed36e..084e58346999 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,6 +1,7 @@ ## NEXT * Updates minimum supported SDK version to Flutter 3.7/Dart 2.19. +* Adds web options to customize control's list and displaying context menu ## 2.0.16 From 7d41cfebd32e9e4c6710118adabb864674bdcabd Mon Sep 17 00:00:00 2001 From: James Leahy Date: Sun, 23 Jul 2023 12:06:12 +0200 Subject: [PATCH 05/12] chore: video_player_web use latest platform interface --- packages/video_player/video_player_web/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index d1554358f83a..efa8e60fd2d2 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - video_player_platform_interface: ">=6.1.0 <7.0.0" + video_player_platform_interface: ">=6.2.0 <7.0.0" dev_dependencies: flutter_test: From 7d3b099c89405fe2f28f67fb5cedd9cd276720ed Mon Sep 17 00:00:00 2001 From: James Leahy Date: Sun, 23 Jul 2023 12:15:54 +0200 Subject: [PATCH 06/12] chore: Add tests --- .../integration_test/video_player_test.dart | 144 ++++++++++++++++++ .../video_player_web_test.dart | 10 ++ 2 files changed, 154 insertions(+) diff --git a/packages/video_player/video_player_web/example/integration_test/video_player_test.dart b/packages/video_player/video_player_web/example/integration_test/video_player_test.dart index 28046f42e9a8..8156b7b48024 100644 --- a/packages/video_player/video_player_web/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player_web/example/integration_test/video_player_test.dart @@ -213,5 +213,149 @@ void main() { expect(events[0].duration, equals(jsCompatibleTimeUnset)); }); }); + + group('VideoPlayerWebOptions', () { + late VideoPlayer player; + + setUp(() { + video = html.VideoElement(); + player = VideoPlayer(videoElement: video)..initialize(); + }); + + group('VideoPlayerWebOptionsControls', () { + testWidgets('when disabled expect no controls', + (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + // ignore: avoid_redundant_argument_values + controls: VideoPlayerWebOptionsControls.disabled(), + ), + ); + + expect(video.controls, isFalse); + expect(video.controlsList, isNotNull); + expect(video.controlsList?.length, isZero); + }); + + group('when enabled', () { + testWidgets('expect controls', (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + controls: VideoPlayerWebOptionsControls.enabled(), + ), + ); + + expect(video.controls, isTrue); + expect(video.controlsList, isNotNull); + expect(video.controlsList?.length, isZero); + expect(video.controlsList?.contains('nodownload'), isFalse); + expect(video.controlsList?.contains('nofullscreen'), isFalse); + expect(video.controlsList?.contains('noplaybackrate'), isFalse); + expect(video.getAttribute('disablePictureInPicture'), isNull); + }); + + testWidgets('and no download expect correct controls', + (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + controls: VideoPlayerWebOptionsControls.enabled( + allowDownload: false, + ), + ), + ); + + expect(video.controls, isTrue); + expect(video.controlsList, isNotNull); + expect(video.controlsList?.length, 1); + expect(video.controlsList?.contains('nodownload'), isTrue); + expect(video.controlsList?.contains('nofullscreen'), isFalse); + expect(video.controlsList?.contains('noplaybackrate'), isFalse); + expect(video.getAttribute('disablePictureInPicture'), isNull); + }); + + testWidgets('and no fullscreen expect correct controls', + (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + controls: VideoPlayerWebOptionsControls.enabled( + allowFullscreen: false, + ), + ), + ); + + expect(video.controls, isTrue); + expect(video.controlsList, isNotNull); + expect(video.controlsList?.length, 1); + expect(video.controlsList?.contains('nodownload'), isFalse); + expect(video.controlsList?.contains('nofullscreen'), isTrue); + expect(video.controlsList?.contains('noplaybackrate'), isFalse); + expect(video.getAttribute('disablePictureInPicture'), isNull); + }); + + testWidgets('and no playback rate expect correct controls', + (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + controls: VideoPlayerWebOptionsControls.enabled( + allowPlaybackRate: false, + ), + ), + ); + + expect(video.controls, isTrue); + expect(video.controlsList, isNotNull); + expect(video.controlsList?.length, 1); + expect(video.controlsList?.contains('nodownload'), isFalse); + expect(video.controlsList?.contains('nofullscreen'), isFalse); + expect(video.controlsList?.contains('noplaybackrate'), isTrue); + expect(video.getAttribute('disablePictureInPicture'), isNull); + }); + + testWidgets('and no picture in picture expect correct controls', + (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + controls: VideoPlayerWebOptionsControls.enabled( + allowPictureInPicture: false, + ), + ), + ); + + expect(video.controls, isTrue); + expect(video.controlsList, isNotNull); + expect(video.controlsList?.length, 0); + expect(video.controlsList?.contains('nodownload'), isFalse); + expect(video.controlsList?.contains('nofullscreen'), isFalse); + expect(video.controlsList?.contains('noplaybackrate'), isFalse); + expect(video.getAttribute('disablePictureInPicture'), 'true'); + }); + }); + }); + + group('allowRemotePlayback', () { + testWidgets('when enabled expect no attribute', + (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + // ignore: avoid_redundant_argument_values + allowRemotePlayback: true, + ), + ); + + expect(video.getAttribute('disableRemotePlayback'), isNull); + }); + + testWidgets('when disabled expect attribute', + (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + allowRemotePlayback: false, + ), + ); + + expect(video.getAttribute('disableRemotePlayback'), 'true'); + }); + }); + }); }); } diff --git a/packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart b/packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart index 7d7422393097..7c8f1c9acefc 100644 --- a/packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart +++ b/packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart @@ -239,5 +239,15 @@ void main() { VideoEventType.bufferingEnd, ])); }); + + testWidgets('can set web options', (WidgetTester tester) async { + expect( + VideoPlayerPlatform.instance.setWebOptions( + await textureId, + const VideoPlayerWebOptions(), + ), + completes, + ); + }); }); } From ba34c45bd0a951209e6d07bee4cde91d3d144a93 Mon Sep 17 00:00:00 2001 From: James Leahy Date: Tue, 25 Jul 2023 08:27:43 +0200 Subject: [PATCH 07/12] feat: ensure setOptions is de-initialized if called twice --- .../integration_test/video_player_test.dart | 50 +++++++++++++++++++ .../lib/src/video_player.dart | 26 +++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/packages/video_player/video_player_web/example/integration_test/video_player_test.dart b/packages/video_player/video_player_web/example/integration_test/video_player_test.dart index 8156b7b48024..d5ede903cda4 100644 --- a/packages/video_player/video_player_web/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player_web/example/integration_test/video_player_test.dart @@ -356,6 +356,56 @@ void main() { expect(video.getAttribute('disableRemotePlayback'), 'true'); }); }); + + group('when called first time', () { + testWidgets('expect correct options', (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + controls: VideoPlayerWebOptionsControls.enabled( + allowDownload: false, + allowFullscreen: false, + allowPlaybackRate: false, + allowPictureInPicture: false, + ), + allowContextMenu: false, + allowRemotePlayback: false, + ), + ); + + expect(video.controls, isTrue); + expect(video.controlsList, isNotNull); + expect(video.controlsList?.length, 3); + expect(video.controlsList?.contains('nodownload'), isTrue); + expect(video.controlsList?.contains('nofullscreen'), isTrue); + expect(video.controlsList?.contains('noplaybackrate'), isTrue); + expect(video.getAttribute('disablePictureInPicture'), 'true'); + expect(video.getAttribute('disableRemotePlayback'), 'true'); + }); + + group('when called once more', () { + testWidgets('expect correct options', (WidgetTester tester) async { + await player.setOptions( + const VideoPlayerWebOptions( + // ignore: avoid_redundant_argument_values + controls: VideoPlayerWebOptionsControls.disabled(), + // ignore: avoid_redundant_argument_values + allowContextMenu: true, + // ignore: avoid_redundant_argument_values + allowRemotePlayback: true, + ), + ); + + expect(video.controls, isFalse); + expect(video.controlsList, isNotNull); + expect(video.controlsList?.length, 0); + expect(video.controlsList?.contains('nodownload'), isFalse); + expect(video.controlsList?.contains('nofullscreen'), isFalse); + expect(video.controlsList?.contains('noplaybackrate'), isFalse); + expect(video.getAttribute('disablePictureInPicture'), isNull); + expect(video.getAttribute('disableRemotePlayback'), isNull); + }); + }); + }); }); }); } diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index 343c136859e2..9eed4d3f0ed5 100644 --- a/packages/video_player/video_player_web/lib/src/video_player.dart +++ b/packages/video_player/video_player_web/lib/src/video_player.dart @@ -205,6 +205,9 @@ class VideoPlayer { /// Sets options Future setOptions(VideoPlayerWebOptions options) async { + // incase called multiple times, reset options + _resetOptions(); + if (options.controls.enabled) { _videoElement.controls = true; final String controlsList = options.controls.controlsList; @@ -227,11 +230,30 @@ class VideoPlayer { } } + void _resetOptions() { + _videoElement.controls = false; + if (_videoElement.hasAttribute('controlsList')) { + _videoElement.setAttribute('controlsList', ''); + } + if (_videoElement.hasAttribute('disablePictureInPicture')) { + _videoElement.setAttribute('disablePictureInPicture', false); + } + if (_onContextMenu != null) { + _videoElement.removeEventListener('contextmenu', _onContextMenu); + _onContextMenu = null; + } + if (_videoElement.hasAttribute('disableRemotePlayback')) { + _videoElement.setAttribute('disableRemotePlayback', false); + } + } + /// Disposes of the current [html.VideoElement]. void dispose() { _videoElement.removeAttribute('src'); - _videoElement.removeEventListener('contextmenu', _onContextMenu); - _onContextMenu = null; + if (_onContextMenu != null) { + _videoElement.removeEventListener('contextmenu', _onContextMenu); + _onContextMenu = null; + } _videoElement.load(); } From d096a67beaab8f4c6171f4e0b20f536544fffdd1 Mon Sep 17 00:00:00 2001 From: James Leahy Date: Thu, 27 Jul 2023 07:32:50 +0200 Subject: [PATCH 08/12] chore: Use removeAttribute --- .../video_player_web/lib/src/video_player.dart | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index 9eed4d3f0ed5..6a85f75d0c2c 100644 --- a/packages/video_player/video_player_web/lib/src/video_player.dart +++ b/packages/video_player/video_player_web/lib/src/video_player.dart @@ -232,19 +232,13 @@ class VideoPlayer { void _resetOptions() { _videoElement.controls = false; - if (_videoElement.hasAttribute('controlsList')) { - _videoElement.setAttribute('controlsList', ''); - } - if (_videoElement.hasAttribute('disablePictureInPicture')) { - _videoElement.setAttribute('disablePictureInPicture', false); - } + _videoElement.removeAttribute('controlsList'); + _videoElement.removeAttribute('disablePictureInPicture'); if (_onContextMenu != null) { _videoElement.removeEventListener('contextmenu', _onContextMenu); _onContextMenu = null; } - if (_videoElement.hasAttribute('disableRemotePlayback')) { - _videoElement.setAttribute('disableRemotePlayback', false); - } + _videoElement.removeAttribute('disableRemotePlayback'); } /// Disposes of the current [html.VideoElement]. From 445fdcd68906fa94e873f7abdda155e4d64838da Mon Sep 17 00:00:00 2001 From: James Leahy Date: Tue, 8 Aug 2023 10:01:30 +0200 Subject: [PATCH 09/12] chore: requested changes --- .../video_player/video_player_web/lib/src/video_player.dart | 2 +- packages/video_player/video_player_web/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index 6a85f75d0c2c..3d047be76581 100644 --- a/packages/video_player/video_player_web/lib/src/video_player.dart +++ b/packages/video_player/video_player_web/lib/src/video_player.dart @@ -205,7 +205,7 @@ class VideoPlayer { /// Sets options Future setOptions(VideoPlayerWebOptions options) async { - // incase called multiple times, reset options + // In case this method is called multiple times, reset options. _resetOptions(); if (options.controls.enabled) { diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index efa8e60fd2d2..5ed6cc98e772 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - video_player_platform_interface: ">=6.2.0 <7.0.0" + video_player_platform_interface: ^6.2.0 dev_dependencies: flutter_test: From 9a1e7ff65b9b75dfccba67b758fe9f4c697a4b64 Mon Sep 17 00:00:00 2001 From: James Leahy Date: Tue, 8 Aug 2023 17:38:04 +0200 Subject: [PATCH 10/12] chore: revert overwritten change --- packages/video_player/video_player_web/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index 084e58346999..a81e4f97325f 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,4 +1,4 @@ -## NEXT +## 2.1.0 * Updates minimum supported SDK version to Flutter 3.7/Dart 2.19. * Adds web options to customize control's list and displaying context menu From d4a12c620981256654e824aae5e4f5544774ed1c Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 1 Sep 2023 09:20:17 -0400 Subject: [PATCH 11/12] Style fixes --- packages/video_player/video_player_web/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index a81e4f97325f..2debc6053376 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,7 +1,7 @@ ## 2.1.0 +* Adds web options to customize the control list and context menu display. * Updates minimum supported SDK version to Flutter 3.7/Dart 2.19. -* Adds web options to customize control's list and displaying context menu ## 2.0.16 From d94124de73871e0667ff7af5398fd671fbd7822e Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 1 Sep 2023 09:21:23 -0400 Subject: [PATCH 12/12] Bump version --- packages/video_player/video_player_web/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 5ed6cc98e772..808211bc9415 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_web description: Web platform implementation of video_player. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.0.16 +version: 2.1.0 environment: sdk: ">=2.19.0 <4.0.0"