Skip to content

Commit 9f0e92f

Browse files
authored
[video_player_web] Listen to loadedmetadata event from video element. (#5289)
This PR configures the underlying HTMLVideoElement used by the plugin so it triggers a `VideoEventType.initialized` both for `canplay` (current behavior) and `loadedmetadata` events (fixes iOS 17). It also sets `src` as the last configuration value when creating the VideoElement, which is common practice when listening to events from HTMLElements, like `img` or even `iframe`s. ### Issues * Fixes: **P1** flutter/flutter#137023 ### Tests * Added a small integration test to ensure `loadedmetadata` triggers the expected VideoPlayer event. * Deployed changes to: https://dit-videoplayer-tests.web.app for manual verification on an actual iOS device.
1 parent e780cb8 commit 9f0e92f

File tree

5 files changed

+62
-11
lines changed

5 files changed

+62
-11
lines changed

packages/video_player/video_player_web/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 2.1.2
2+
3+
* Listens to `loadedmetadata` as an event that marks that initialization is
4+
complete. (Fixes playback in Safari iOS 17).
5+
* Sets the `src` of the underlying video element after every other attribute.
6+
17
## 2.1.1
28

39
* Ensures that the `autoplay` attribute of the underlying video element is set

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,23 @@ void main() {
197197
expect(events[0].eventType, VideoEventType.initialized);
198198
});
199199

200+
// Issue: https://github.com/flutter/flutter/issues/137023
201+
testWidgets('loadedmetadata dispatches initialized',
202+
(WidgetTester tester) async {
203+
video.dispatchEvent(html.Event('loadedmetadata'));
204+
video.dispatchEvent(html.Event('loadedmetadata'));
205+
206+
final Future<List<VideoEvent>> stream = timedStream
207+
.where((VideoEvent event) =>
208+
event.eventType == VideoEventType.initialized)
209+
.toList();
210+
211+
final List<VideoEvent> events = await stream;
212+
213+
expect(events, hasLength(1));
214+
expect(events[0].eventType, VideoEventType.initialized);
215+
});
216+
200217
// Issue: https://github.com/flutter/flutter/issues/105649
201218
testWidgets('supports `Infinity` duration', (WidgetTester _) async {
202219
setInfinityDuration(video);

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

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,17 @@ class VideoPlayer {
5858
/// This method sets the required DOM attributes so videos can [play] programmatically,
5959
/// and attaches listeners to the internal events from the [html.VideoElement]
6060
/// to react to them / expose them through the [VideoPlayer.events] stream.
61-
void initialize() {
61+
///
62+
/// The [src] parameter is the URL of the video. It is passed in from the plugin
63+
/// `create` method so it can be set in the VideoElement *last*. This way, all
64+
/// the event listeners needed to integrate the videoElement with the plugin
65+
/// are attached before any events start firing (events start to fire when the
66+
/// `src` attribute is set).
67+
///
68+
/// The `src` parameter is nullable for testing purposes.
69+
void initialize({
70+
String? src,
71+
}) {
6272
_videoElement
6373
..autoplay = false
6474
..controls = false;
@@ -68,14 +78,11 @@ class VideoPlayer {
6878
// This property is not exposed through dart:html so we use the
6979
// HTML Boolean attribute form (when present with any value => true)
7080
// See: https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML
71-
_videoElement.setAttribute('playsinline', 'true');
81+
_videoElement.setAttribute('playsinline', true);
7282

73-
_videoElement.onCanPlay.listen((dynamic _) {
74-
if (!_isInitialized) {
75-
_isInitialized = true;
76-
_sendInitialized();
77-
}
78-
});
83+
_videoElement.onCanPlay.listen(_onVideoElementInitialization);
84+
// Needed for Safari iOS 17, which may not send `canplay`.
85+
_videoElement.onLoadedMetadata.listen(_onVideoElementInitialization);
7986

8087
_videoElement.onCanPlayThrough.listen((dynamic _) {
8188
setBuffering(false);
@@ -122,6 +129,12 @@ class VideoPlayer {
122129
setBuffering(false);
123130
_eventController.add(VideoEvent(eventType: VideoEventType.completed));
124131
});
132+
133+
// The `src` of the _videoElement is the last property that is set, so all
134+
// the listeners for the events that the plugin cares about are attached.
135+
if (src != null) {
136+
_videoElement.src = src;
137+
}
125138
}
126139

127140
/// Attempts to play the video.
@@ -252,6 +265,20 @@ class VideoPlayer {
252265
_videoElement.load();
253266
}
254267

268+
// Handler to mark (and broadcast) when this player [_isInitialized].
269+
//
270+
// (Used as a JS event handler for "canplay" and "loadedmetadata")
271+
//
272+
// This function can be called multiple times by different JS Events, but it'll
273+
// only broadcast an "initialized" event the first time it's called, and ignore
274+
// the rest of the calls.
275+
void _onVideoElementInitialization(Object? _) {
276+
if (!_isInitialized) {
277+
_isInitialized = true;
278+
_sendInitialized();
279+
}
280+
}
281+
255282
// Sends an [VideoEventType.initialized] [VideoEvent] with info about the wrapped video.
256283
void _sendInitialized() {
257284
final Duration? duration =

packages/video_player/video_player_web/lib/video_player_web.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ class VideoPlayerPlugin extends VideoPlayerPlatform {
7575

7676
final VideoElement videoElement = VideoElement()
7777
..id = 'videoElement-$textureId'
78-
..src = uri
7978
..style.border = 'none'
8079
..style.height = '100%'
8180
..style.width = '100%';
@@ -85,7 +84,9 @@ class VideoPlayerPlugin extends VideoPlayerPlatform {
8584
'videoPlayer-$textureId', (int viewId) => videoElement);
8685

8786
final VideoPlayer player = VideoPlayer(videoElement: videoElement)
88-
..initialize();
87+
..initialize(
88+
src: uri,
89+
);
8990

9091
_videoPlayers[textureId] = player;
9192

packages/video_player/video_player_web/pubspec.yaml

Lines changed: 1 addition & 1 deletion
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.1.1
5+
version: 2.1.2
66

77
environment:
88
sdk: ">=3.1.0 <4.0.0"

0 commit comments

Comments
 (0)