Skip to content

Commit 8bf9abb

Browse files
authored
[video_player] Add optional web options [web] (flutter#4551)
Web PR for Video Player Web Options (flutter/packages#3259).
1 parent 85d9ce3 commit 8bf9abb

File tree

6 files changed

+258
-2
lines changed

6 files changed

+258
-2
lines changed

packages/video_player/video_player_web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.1.0
2+
3+
* Adds web options to customize the control list and context menu display.
4+
15
## 2.0.18
26

37
* Migrates to `dart:ui_web` APIs.

packages/video_player/video_player_web/example/integration_test/video_player_test.dart

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,5 +213,199 @@ void main() {
213213
expect(events[0].duration, equals(jsCompatibleTimeUnset));
214214
});
215215
});
216+
217+
group('VideoPlayerWebOptions', () {
218+
late VideoPlayer player;
219+
220+
setUp(() {
221+
video = html.VideoElement();
222+
player = VideoPlayer(videoElement: video)..initialize();
223+
});
224+
225+
group('VideoPlayerWebOptionsControls', () {
226+
testWidgets('when disabled expect no controls',
227+
(WidgetTester tester) async {
228+
await player.setOptions(
229+
const VideoPlayerWebOptions(
230+
// ignore: avoid_redundant_argument_values
231+
controls: VideoPlayerWebOptionsControls.disabled(),
232+
),
233+
);
234+
235+
expect(video.controls, isFalse);
236+
expect(video.controlsList, isNotNull);
237+
expect(video.controlsList?.length, isZero);
238+
});
239+
240+
group('when enabled', () {
241+
testWidgets('expect controls', (WidgetTester tester) async {
242+
await player.setOptions(
243+
const VideoPlayerWebOptions(
244+
controls: VideoPlayerWebOptionsControls.enabled(),
245+
),
246+
);
247+
248+
expect(video.controls, isTrue);
249+
expect(video.controlsList, isNotNull);
250+
expect(video.controlsList?.length, isZero);
251+
expect(video.controlsList?.contains('nodownload'), isFalse);
252+
expect(video.controlsList?.contains('nofullscreen'), isFalse);
253+
expect(video.controlsList?.contains('noplaybackrate'), isFalse);
254+
expect(video.getAttribute('disablePictureInPicture'), isNull);
255+
});
256+
257+
testWidgets('and no download expect correct controls',
258+
(WidgetTester tester) async {
259+
await player.setOptions(
260+
const VideoPlayerWebOptions(
261+
controls: VideoPlayerWebOptionsControls.enabled(
262+
allowDownload: false,
263+
),
264+
),
265+
);
266+
267+
expect(video.controls, isTrue);
268+
expect(video.controlsList, isNotNull);
269+
expect(video.controlsList?.length, 1);
270+
expect(video.controlsList?.contains('nodownload'), isTrue);
271+
expect(video.controlsList?.contains('nofullscreen'), isFalse);
272+
expect(video.controlsList?.contains('noplaybackrate'), isFalse);
273+
expect(video.getAttribute('disablePictureInPicture'), isNull);
274+
});
275+
276+
testWidgets('and no fullscreen expect correct controls',
277+
(WidgetTester tester) async {
278+
await player.setOptions(
279+
const VideoPlayerWebOptions(
280+
controls: VideoPlayerWebOptionsControls.enabled(
281+
allowFullscreen: false,
282+
),
283+
),
284+
);
285+
286+
expect(video.controls, isTrue);
287+
expect(video.controlsList, isNotNull);
288+
expect(video.controlsList?.length, 1);
289+
expect(video.controlsList?.contains('nodownload'), isFalse);
290+
expect(video.controlsList?.contains('nofullscreen'), isTrue);
291+
expect(video.controlsList?.contains('noplaybackrate'), isFalse);
292+
expect(video.getAttribute('disablePictureInPicture'), isNull);
293+
});
294+
295+
testWidgets('and no playback rate expect correct controls',
296+
(WidgetTester tester) async {
297+
await player.setOptions(
298+
const VideoPlayerWebOptions(
299+
controls: VideoPlayerWebOptionsControls.enabled(
300+
allowPlaybackRate: false,
301+
),
302+
),
303+
);
304+
305+
expect(video.controls, isTrue);
306+
expect(video.controlsList, isNotNull);
307+
expect(video.controlsList?.length, 1);
308+
expect(video.controlsList?.contains('nodownload'), isFalse);
309+
expect(video.controlsList?.contains('nofullscreen'), isFalse);
310+
expect(video.controlsList?.contains('noplaybackrate'), isTrue);
311+
expect(video.getAttribute('disablePictureInPicture'), isNull);
312+
});
313+
314+
testWidgets('and no picture in picture expect correct controls',
315+
(WidgetTester tester) async {
316+
await player.setOptions(
317+
const VideoPlayerWebOptions(
318+
controls: VideoPlayerWebOptionsControls.enabled(
319+
allowPictureInPicture: false,
320+
),
321+
),
322+
);
323+
324+
expect(video.controls, isTrue);
325+
expect(video.controlsList, isNotNull);
326+
expect(video.controlsList?.length, 0);
327+
expect(video.controlsList?.contains('nodownload'), isFalse);
328+
expect(video.controlsList?.contains('nofullscreen'), isFalse);
329+
expect(video.controlsList?.contains('noplaybackrate'), isFalse);
330+
expect(video.getAttribute('disablePictureInPicture'), 'true');
331+
});
332+
});
333+
});
334+
335+
group('allowRemotePlayback', () {
336+
testWidgets('when enabled expect no attribute',
337+
(WidgetTester tester) async {
338+
await player.setOptions(
339+
const VideoPlayerWebOptions(
340+
// ignore: avoid_redundant_argument_values
341+
allowRemotePlayback: true,
342+
),
343+
);
344+
345+
expect(video.getAttribute('disableRemotePlayback'), isNull);
346+
});
347+
348+
testWidgets('when disabled expect attribute',
349+
(WidgetTester tester) async {
350+
await player.setOptions(
351+
const VideoPlayerWebOptions(
352+
allowRemotePlayback: false,
353+
),
354+
);
355+
356+
expect(video.getAttribute('disableRemotePlayback'), 'true');
357+
});
358+
});
359+
360+
group('when called first time', () {
361+
testWidgets('expect correct options', (WidgetTester tester) async {
362+
await player.setOptions(
363+
const VideoPlayerWebOptions(
364+
controls: VideoPlayerWebOptionsControls.enabled(
365+
allowDownload: false,
366+
allowFullscreen: false,
367+
allowPlaybackRate: false,
368+
allowPictureInPicture: false,
369+
),
370+
allowContextMenu: false,
371+
allowRemotePlayback: false,
372+
),
373+
);
374+
375+
expect(video.controls, isTrue);
376+
expect(video.controlsList, isNotNull);
377+
expect(video.controlsList?.length, 3);
378+
expect(video.controlsList?.contains('nodownload'), isTrue);
379+
expect(video.controlsList?.contains('nofullscreen'), isTrue);
380+
expect(video.controlsList?.contains('noplaybackrate'), isTrue);
381+
expect(video.getAttribute('disablePictureInPicture'), 'true');
382+
expect(video.getAttribute('disableRemotePlayback'), 'true');
383+
});
384+
385+
group('when called once more', () {
386+
testWidgets('expect correct options', (WidgetTester tester) async {
387+
await player.setOptions(
388+
const VideoPlayerWebOptions(
389+
// ignore: avoid_redundant_argument_values
390+
controls: VideoPlayerWebOptionsControls.disabled(),
391+
// ignore: avoid_redundant_argument_values
392+
allowContextMenu: true,
393+
// ignore: avoid_redundant_argument_values
394+
allowRemotePlayback: true,
395+
),
396+
);
397+
398+
expect(video.controls, isFalse);
399+
expect(video.controlsList, isNotNull);
400+
expect(video.controlsList?.length, 0);
401+
expect(video.controlsList?.contains('nodownload'), isFalse);
402+
expect(video.controlsList?.contains('nofullscreen'), isFalse);
403+
expect(video.controlsList?.contains('noplaybackrate'), isFalse);
404+
expect(video.getAttribute('disablePictureInPicture'), isNull);
405+
expect(video.getAttribute('disableRemotePlayback'), isNull);
406+
});
407+
});
408+
});
409+
});
216410
});
217411
}

packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,5 +239,15 @@ void main() {
239239
VideoEventType.bufferingEnd,
240240
]));
241241
});
242+
243+
testWidgets('can set web options', (WidgetTester tester) async {
244+
expect(
245+
VideoPlayerPlatform.instance.setWebOptions(
246+
await textureId,
247+
const VideoPlayerWebOptions(),
248+
),
249+
completes,
250+
);
251+
});
242252
});
243253
}

packages/video_player/video_player_web/lib/src/video_player.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class VideoPlayer {
4545

4646
final StreamController<VideoEvent> _eventController;
4747
final html.VideoElement _videoElement;
48+
void Function(html.Event)? _onContextMenu;
4849

4950
bool _isInitialized = false;
5051
bool _isBuffering = false;
@@ -202,9 +203,51 @@ class VideoPlayer {
202203
return Duration(milliseconds: (_videoElement.currentTime * 1000).round());
203204
}
204205

206+
/// Sets options
207+
Future<void> setOptions(VideoPlayerWebOptions options) async {
208+
// In case this method is called multiple times, reset options.
209+
_resetOptions();
210+
211+
if (options.controls.enabled) {
212+
_videoElement.controls = true;
213+
final String controlsList = options.controls.controlsList;
214+
if (controlsList.isNotEmpty) {
215+
_videoElement.setAttribute('controlsList', controlsList);
216+
}
217+
218+
if (!options.controls.allowPictureInPicture) {
219+
_videoElement.setAttribute('disablePictureInPicture', true);
220+
}
221+
}
222+
223+
if (!options.allowContextMenu) {
224+
_onContextMenu = (html.Event event) => event.preventDefault();
225+
_videoElement.addEventListener('contextmenu', _onContextMenu);
226+
}
227+
228+
if (!options.allowRemotePlayback) {
229+
_videoElement.setAttribute('disableRemotePlayback', true);
230+
}
231+
}
232+
233+
void _resetOptions() {
234+
_videoElement.controls = false;
235+
_videoElement.removeAttribute('controlsList');
236+
_videoElement.removeAttribute('disablePictureInPicture');
237+
if (_onContextMenu != null) {
238+
_videoElement.removeEventListener('contextmenu', _onContextMenu);
239+
_onContextMenu = null;
240+
}
241+
_videoElement.removeAttribute('disableRemotePlayback');
242+
}
243+
205244
/// Disposes of the current [html.VideoElement].
206245
void dispose() {
207246
_videoElement.removeAttribute('src');
247+
if (_onContextMenu != null) {
248+
_videoElement.removeEventListener('contextmenu', _onContextMenu);
249+
_onContextMenu = null;
250+
}
208251
_videoElement.load();
209252
}
210253

packages/video_player/video_player_web/lib/video_player_web.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ class VideoPlayerPlugin extends VideoPlayerPlatform {
132132
return _player(textureId).events;
133133
}
134134

135+
@override
136+
Future<void> setWebOptions(int textureId, VideoPlayerWebOptions options) {
137+
return _player(textureId).setOptions(options);
138+
}
139+
135140
// Retrieves a [VideoPlayer] by its internal `id`.
136141
// It must have been created earlier from the [create] method.
137142
VideoPlayer _player(int id) {

packages/video_player/video_player_web/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: video_player_web
22
description: Web platform implementation of video_player.
33
repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_web
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
5-
version: 2.0.18
5+
version: 2.1.0
66

77
environment:
88
sdk: ">=3.1.0 <4.0.0"
@@ -21,7 +21,7 @@ dependencies:
2121
sdk: flutter
2222
flutter_web_plugins:
2323
sdk: flutter
24-
video_player_platform_interface: ">=6.1.0 <7.0.0"
24+
video_player_platform_interface: ^6.2.0
2525

2626
dev_dependencies:
2727
flutter_test:

0 commit comments

Comments
 (0)