Skip to content

[video_player_web] migrates to package:web #5800

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
76c9178
migrate to package:web'
balvinderz Jan 4, 2024
ee17974
fix tests
balvinderz Jan 4, 2024
aca2d65
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 5, 2024
613edb8
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 5, 2024
d99e782
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 6, 2024
b6ebf4d
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 7, 2024
db93947
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 9, 2024
cff2dad
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 10, 2024
52711d8
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 13, 2024
ddb7c8c
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 14, 2024
d930659
Merge branch 'main' into migrate_video_player_web_to_package_web
balvinderz Jan 27, 2024
b66ad37
Merge branch 'main' into migrate_video_player_web_to_package_web
ditman Mar 1, 2024
2a6f7bf
Ensure web 0.5.0 everywhere. Update some syntax.
ditman Mar 1, 2024
54fa27e
Address some additional PR comments.
ditman Mar 1, 2024
dcfdd7f
Comment some public integration_test utils.
ditman Mar 5, 2024
66fac4b
Incorporate #5920
ditman Mar 5, 2024
0df1bc7
Move all js-interop code together.
ditman Mar 5, 2024
46de6a4
Fix version in pubspec
ditman Mar 5, 2024
529e718
Merge branch 'main' into migrate_video_player_web_to_package_web
ditman Mar 5, 2024
c898c83
Use HTMLElement constructors from web 0.5.1
ditman Mar 5, 2024
66a9db2
Format
ditman Mar 6, 2024
9b02258
Improves JS-interop for defineProperty.
ditman Mar 6, 2024
06fab2e
Consolidated extension types.
ditman Mar 6, 2024
2ba0a9c
Use web 0.5.1 for test app too.
ditman Mar 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/video_player/video_player_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.3.0

* Migrates package and tests to `package:web``.
* Fixes infinite event loop caused by `seekTo` when the video ends.

## 2.2.0

* Updates SDK version to Dart `^3.3.0`. Flutter `^3.19.0`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@JS()
library video_player_web_integration_test_pkg_web_tweaks;

import 'dart:js_interop';
import 'package:web/web.dart' as web;

/// Adds a `controlsList` and `disablePictureInPicture` getters.
extension NonStandardGettersOnVideoElement on web.HTMLVideoElement {
external web.DOMTokenList? get controlsList;
external JSBoolean get disablePictureInPicture;
}

/// Adds a `disableRemotePlayback` getter.
extension NonStandardGettersOnMediaElement on web.HTMLMediaElement {
external JSBoolean get disableRemotePlayback;
}

/// Defines JS interop to access static methods from `Object`.
@JS('Object')
extension type DomObject._(JSAny _) {
@JS('defineProperty')
external static void _defineProperty(
JSAny? object, JSString property, Descriptor value);

/// `Object.defineProperty`.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
static void defineProperty(
JSObject object, String property, Descriptor descriptor) {
return _defineProperty(object, property.toJS, descriptor);
}
}

/// The descriptor for the property being defined or modified with `defineProperty`.
///
/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description
extension type Descriptor._(JSObject _) implements JSObject {
/// Builds a "data descriptor".
factory Descriptor.data({
bool? writable,
JSAny? value,
}) =>
Descriptor._data(
writable: writable?.toJS,
value: value.jsify(),
);

/// Builds an "accessor descriptor".
factory Descriptor.accessor({
void Function(JSAny? value)? set,
JSAny? Function()? get,
}) =>
Descriptor._accessor(
set: set?.toJS,
get: get?.toJS,
);

external factory Descriptor._accessor({
// JSBoolean configurable,
// JSBoolean enumerable,
JSFunction? set,
JSFunction? get,
});

external factory Descriptor._data({
// JSBoolean configurable,
// JSBoolean enumerable,
JSBoolean? writable,
JSAny? value,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
// found in the LICENSE file.

import 'dart:js_interop';
import 'dart:js_interop_unsafe';

import 'package:web/web.dart' as web;
import 'pkg_web_tweaks.dart';

// Returns the URL to load an asset from this example app as a network source.
//
Expand All @@ -19,40 +20,29 @@ String getUrlForAssetAsNetworkSource(String assetKey) {
'?raw=true';
}

extension type Descriptor._(JSObject _) implements JSObject {
// May also contain "configurable" and "enumerable" bools.
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description
external factory Descriptor({
// bool configurable,
// bool enumerable,
JSBoolean writable,
JSAny value,
});
}

void _defineProperty(
Object object,
String property,
Descriptor description,
) {
(globalContext['Object'] as JSObject?)?.callMethod(
'defineProperty'.toJS,
object as JSObject,
property.toJS,
description,
);
}

/// Forces a VideoElement to report "Infinity" duration.
///
/// Uses JS Object.defineProperty to set the value of a readonly property.
void setInfinityDuration(Object videoElement) {
assert(videoElement is web.HTMLVideoElement);
_defineProperty(
videoElement,
'duration',
Descriptor(
writable: true.toJS,
value: double.infinity.toJS,
void setInfinityDuration(web.HTMLVideoElement element) {
DomObject.defineProperty(
element,
'duration',
Descriptor.data(
writable: true,
value: double.infinity.toJS,
),
);
}

/// Makes the `currentTime` setter throw an exception if used.
void makeSetCurrentTimeThrow(web.HTMLVideoElement element) {
DomObject.defineProperty(
element,
'currentTime',
Descriptor.accessor(
set: (JSAny? value) {
throw Exception('Unexpected call to currentTime with value: $value');
},
get: () => 100.toJS,
));
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,28 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:html' as html;

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
import 'package:video_player_web/src/duration_utils.dart';
import 'package:video_player_web/src/video_player.dart';
import 'package:web/web.dart' as web;

import 'pkg_web_tweaks.dart';
import 'utils.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('VideoPlayer', () {
late html.VideoElement video;
late web.HTMLVideoElement video;

setUp(() {
// Never set "src" on the video, so this test doesn't hit the network!
video = html.VideoElement()
video = web.HTMLVideoElement()
..controls = true
..setAttribute('playsinline', 'false');
..playsInline = false;
});

testWidgets('fixes critical video element config', (WidgetTester _) async {
Expand All @@ -36,8 +37,7 @@ void main() {
// see: https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML
expect(video.getAttribute('autoplay'), isNull,
reason: 'autoplay attribute on video tag must NOT be set');
expect(video.getAttribute('playsinline'), 'true',
reason: 'Needed by safari iOS');
expect(video.playsInline, true, reason: 'Needed by safari iOS');
});

testWidgets('setVolume', (WidgetTester tester) async {
Expand Down Expand Up @@ -69,12 +69,32 @@ void main() {
}, throwsAssertionError, reason: 'Playback speed cannot be == 0');
});

testWidgets('seekTo', (WidgetTester tester) async {
final VideoPlayer player = VideoPlayer(videoElement: video)..initialize();
group('seekTo', () {
testWidgets('negative time - throws assert', (WidgetTester tester) async {
final VideoPlayer player = VideoPlayer(videoElement: video)
..initialize();

expect(() {
player.seekTo(const Duration(seconds: -1));
}, throwsAssertionError, reason: 'Cannot seek into negative numbers');
expect(() {
player.seekTo(const Duration(seconds: -1));
}, throwsAssertionError, reason: 'Cannot seek into negative numbers');
});

testWidgets('setting currentTime to its current value - noop',
(WidgetTester tester) async {
makeSetCurrentTimeThrow(video);
final VideoPlayer player = VideoPlayer(videoElement: video)
..initialize();

expect(() {
// Self-test...
video.currentTime = 123;
}, throwsException, reason: 'Setting currentTime must throw!');

expect(() {
// Should not set currentTime (and throw) when seekTo current time.
player.seekTo(Duration(seconds: video.currentTime.toInt()));
}, returnsNormally);
});
});

// The events tested in this group do *not* represent the actual sequence
Expand Down Expand Up @@ -145,7 +165,7 @@ void main() {
player.setBuffering(true);

// Simulate "canplay" event...
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(web.Event('canplay'));

final List<bool> events = await stream;

Expand All @@ -166,7 +186,7 @@ void main() {
player.setBuffering(true);

// Simulate "canplaythrough" event...
video.dispatchEvent(html.Event('canplaythrough'));
video.dispatchEvent(web.Event('canplaythrough'));

final List<bool> events = await stream;

Expand All @@ -177,19 +197,19 @@ void main() {
testWidgets('initialized dispatches only once',
(WidgetTester tester) async {
// Dispatch some bogus "canplay" events from the video object
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(web.Event('canplay'));
video.dispatchEvent(web.Event('canplay'));
video.dispatchEvent(web.Event('canplay'));

// Take all the "initialized" events that we see during the next few seconds
final Future<List<VideoEvent>> stream = timedStream
.where((VideoEvent event) =>
event.eventType == VideoEventType.initialized)
.toList();

video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(web.Event('canplay'));
video.dispatchEvent(web.Event('canplay'));
video.dispatchEvent(web.Event('canplay'));

final List<VideoEvent> events = await stream;

Expand All @@ -200,8 +220,8 @@ void main() {
// Issue: https://github.com/flutter/flutter/issues/137023
testWidgets('loadedmetadata dispatches initialized',
(WidgetTester tester) async {
video.dispatchEvent(html.Event('loadedmetadata'));
video.dispatchEvent(html.Event('loadedmetadata'));
video.dispatchEvent(web.Event('loadedmetadata'));
video.dispatchEvent(web.Event('loadedmetadata'));

final Future<List<VideoEvent>> stream = timedStream
.where((VideoEvent event) =>
Expand All @@ -224,7 +244,7 @@ void main() {
event.eventType == VideoEventType.initialized)
.toList();

video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(web.Event('canplay'));

final List<VideoEvent> events = await stream;

Expand All @@ -238,7 +258,7 @@ void main() {
late VideoPlayer player;

setUp(() {
video = html.VideoElement();
video = web.HTMLVideoElement();
player = VideoPlayer(videoElement: video)..initialize();
});

Expand Down Expand Up @@ -271,7 +291,7 @@ void main() {
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.disablePictureInPicture, isFalse);
});

testWidgets('and no download expect correct controls',
Expand All @@ -290,7 +310,7 @@ void main() {
expect(video.controlsList?.contains('nodownload'), isTrue);
expect(video.controlsList?.contains('nofullscreen'), isFalse);
expect(video.controlsList?.contains('noplaybackrate'), isFalse);
expect(video.getAttribute('disablePictureInPicture'), isNull);
expect(video.disablePictureInPicture, isFalse);
});

testWidgets('and no fullscreen expect correct controls',
Expand All @@ -309,7 +329,7 @@ void main() {
expect(video.controlsList?.contains('nodownload'), isFalse);
expect(video.controlsList?.contains('nofullscreen'), isTrue);
expect(video.controlsList?.contains('noplaybackrate'), isFalse);
expect(video.getAttribute('disablePictureInPicture'), isNull);
expect(video.disablePictureInPicture, isFalse);
});

testWidgets('and no playback rate expect correct controls',
Expand All @@ -328,7 +348,7 @@ void main() {
expect(video.controlsList?.contains('nodownload'), isFalse);
expect(video.controlsList?.contains('nofullscreen'), isFalse);
expect(video.controlsList?.contains('noplaybackrate'), isTrue);
expect(video.getAttribute('disablePictureInPicture'), isNull);
expect(video.disablePictureInPicture, isFalse);
});

testWidgets('and no picture in picture expect correct controls',
Expand All @@ -347,7 +367,7 @@ void main() {
expect(video.controlsList?.contains('nodownload'), isFalse);
expect(video.controlsList?.contains('nofullscreen'), isFalse);
expect(video.controlsList?.contains('noplaybackrate'), isFalse);
expect(video.getAttribute('disablePictureInPicture'), 'true');
expect(video.disablePictureInPicture, isTrue);
});
});
});
Expand All @@ -362,7 +382,7 @@ void main() {
),
);

expect(video.getAttribute('disableRemotePlayback'), isNull);
expect(video.disableRemotePlayback, isFalse);
});

testWidgets('when disabled expect attribute',
Expand All @@ -373,7 +393,7 @@ void main() {
),
);

expect(video.getAttribute('disableRemotePlayback'), 'true');
expect(video.disableRemotePlayback, isTrue);
});
});

Expand All @@ -398,8 +418,8 @@ void main() {
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');
expect(video.disablePictureInPicture, isTrue);
expect(video.disableRemotePlayback, isTrue);
});

group('when called once more', () {
Expand All @@ -421,8 +441,8 @@ void main() {
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);
expect(video.disablePictureInPicture, isFalse);
expect(video.disableRemotePlayback, isFalse);
});
});
});
Expand Down
Loading