Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[video_player] Add optional web options [web] #4551

4 changes: 4 additions & 0 deletions packages/video_player/video_player_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.1.0

* Adds web options to customize the control list and context menu display.

## 2.0.18

* Migrates to `dart:ui_web` APIs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,199 @@ 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');
});
});

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);
});
});
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class VideoPlayer {

final StreamController<VideoEvent> _eventController;
final html.VideoElement _videoElement;
void Function(html.Event)? _onContextMenu;

bool _isInitialized = false;
bool _isBuffering = false;
Expand Down Expand Up @@ -202,9 +203,51 @@ class VideoPlayer {
return Duration(milliseconds: (_videoElement.currentTime * 1000).round());
}

/// Sets options
Future<void> setOptions(VideoPlayerWebOptions options) async {
defuncart marked this conversation as resolved.
Show resolved Hide resolved
// In case this method is called multiple times, reset options.
_resetOptions();

if (options.controls.enabled) {
_videoElement.controls = true;
final String controlsList = options.controls.controlsList;
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);
}
}

void _resetOptions() {
_videoElement.controls = false;
_videoElement.removeAttribute('controlsList');
_videoElement.removeAttribute('disablePictureInPicture');
if (_onContextMenu != null) {
_videoElement.removeEventListener('contextmenu', _onContextMenu);
_onContextMenu = null;
}
_videoElement.removeAttribute('disableRemotePlayback');
}

/// Disposes of the current [html.VideoElement].
void dispose() {
_videoElement.removeAttribute('src');
if (_onContextMenu != null) {
_videoElement.removeEventListener('contextmenu', _onContextMenu);
_onContextMenu = null;
}
_videoElement.load();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ class VideoPlayerPlugin extends VideoPlayerPlatform {
return _player(textureId).events;
}

@override
Future<void> 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) {
Expand Down
4 changes: 2 additions & 2 deletions packages/video_player/video_player_web/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.18
version: 2.1.0

environment:
sdk: ">=3.1.0 <4.0.0"
Expand All @@ -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

dev_dependencies:
flutter_test:
Expand Down