Skip to content

Commit

Permalink
[video_player_web] Listen to loadedmetadata event from video element. (
Browse files Browse the repository at this point in the history
…flutter#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#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.
  • Loading branch information
ditman authored Nov 1, 2023
1 parent e780cb8 commit 9f0e92f
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 11 deletions.
6 changes: 6 additions & 0 deletions packages/video_player/video_player_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 2.1.2

* Listens to `loadedmetadata` as an event that marks that initialization is
complete. (Fixes playback in Safari iOS 17).
* Sets the `src` of the underlying video element after every other attribute.

## 2.1.1

* Ensures that the `autoplay` attribute of the underlying video element is set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,23 @@ void main() {
expect(events[0].eventType, VideoEventType.initialized);
});

// 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'));

final Future<List<VideoEvent>> stream = timedStream
.where((VideoEvent event) =>
event.eventType == VideoEventType.initialized)
.toList();

final List<VideoEvent> events = await stream;

expect(events, hasLength(1));
expect(events[0].eventType, VideoEventType.initialized);
});

// Issue: https://github.com/flutter/flutter/issues/105649
testWidgets('supports `Infinity` duration', (WidgetTester _) async {
setInfinityDuration(video);
Expand Down
43 changes: 35 additions & 8 deletions packages/video_player/video_player_web/lib/src/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,17 @@ class VideoPlayer {
/// This method sets the required DOM attributes so videos can [play] programmatically,
/// and attaches listeners to the internal events from the [html.VideoElement]
/// to react to them / expose them through the [VideoPlayer.events] stream.
void initialize() {
///
/// The [src] parameter is the URL of the video. It is passed in from the plugin
/// `create` method so it can be set in the VideoElement *last*. This way, all
/// the event listeners needed to integrate the videoElement with the plugin
/// are attached before any events start firing (events start to fire when the
/// `src` attribute is set).
///
/// The `src` parameter is nullable for testing purposes.
void initialize({
String? src,
}) {
_videoElement
..autoplay = false
..controls = false;
Expand All @@ -68,14 +78,11 @@ class VideoPlayer {
// This property is not exposed through dart:html so we use the
// HTML Boolean attribute form (when present with any value => true)
// See: https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML
_videoElement.setAttribute('playsinline', 'true');
_videoElement.setAttribute('playsinline', true);

_videoElement.onCanPlay.listen((dynamic _) {
if (!_isInitialized) {
_isInitialized = true;
_sendInitialized();
}
});
_videoElement.onCanPlay.listen(_onVideoElementInitialization);
// Needed for Safari iOS 17, which may not send `canplay`.
_videoElement.onLoadedMetadata.listen(_onVideoElementInitialization);

_videoElement.onCanPlayThrough.listen((dynamic _) {
setBuffering(false);
Expand Down Expand Up @@ -122,6 +129,12 @@ class VideoPlayer {
setBuffering(false);
_eventController.add(VideoEvent(eventType: VideoEventType.completed));
});

// The `src` of the _videoElement is the last property that is set, so all
// the listeners for the events that the plugin cares about are attached.
if (src != null) {
_videoElement.src = src;
}
}

/// Attempts to play the video.
Expand Down Expand Up @@ -252,6 +265,20 @@ class VideoPlayer {
_videoElement.load();
}

// Handler to mark (and broadcast) when this player [_isInitialized].
//
// (Used as a JS event handler for "canplay" and "loadedmetadata")
//
// This function can be called multiple times by different JS Events, but it'll
// only broadcast an "initialized" event the first time it's called, and ignore
// the rest of the calls.
void _onVideoElementInitialization(Object? _) {
if (!_isInitialized) {
_isInitialized = true;
_sendInitialized();
}
}

// Sends an [VideoEventType.initialized] [VideoEvent] with info about the wrapped video.
void _sendInitialized() {
final Duration? duration =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ class VideoPlayerPlugin extends VideoPlayerPlatform {

final VideoElement videoElement = VideoElement()
..id = 'videoElement-$textureId'
..src = uri
..style.border = 'none'
..style.height = '100%'
..style.width = '100%';
Expand All @@ -85,7 +84,9 @@ class VideoPlayerPlugin extends VideoPlayerPlatform {
'videoPlayer-$textureId', (int viewId) => videoElement);

final VideoPlayer player = VideoPlayer(videoElement: videoElement)
..initialize();
..initialize(
src: uri,
);

_videoPlayers[textureId] = player;

Expand Down
2 changes: 1 addition & 1 deletion 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.1.1
version: 2.1.2

environment:
sdk: ">=3.1.0 <4.0.0"
Expand Down

0 comments on commit 9f0e92f

Please sign in to comment.