[video_player_avfoundation] send video load failure even when eventsink was initialized late (#7194)
Event `initialized` is sent from `onListenWithArguments` by calling `setupEventSinkIfReadyToPlay` if event sink was `nil` during `observeValueForKeyPath`. This change also sends failure in a similar way. Adds more details to the error message returned by `VideoPlayerController.initialize()`.
- https://github.com/flutter/flutter/issues/151475
- https://github.com/flutter/flutter/issues/147707
- https://github.com/flutter/flutter/issues/56665 - now error message may look like: `Failed to load video: Operation Stopped: The server is not correctly configured.: The operation couldnât be completed. (CoreMediaErrorDomain error -12939 - byte range and no content length - error code is 200)`
Tests `testSeekToWhilePausedStartsDisplayLinkTemporarily` and `testSeekToWhilePlayingDoesNotStopDisplayLink` are failing on the main branch on ios. Seems position is correctly set to 1234 in `seekTo` completion handler but right after `[mockDisplayLink setRunning:YES]` is set again to 0 and after some time back to 1234 so those two tests sometimes fail (seems more often when running all tests).
diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md
index 8457977..fe47d79 100644
--- a/packages/video_player/video_player_avfoundation/CHANGELOG.md
+++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.6.3
+
+* Fixes VideoPlayerController.initialize() future never resolving with invalid video file.
+* Adds more details to the error message returned by VideoPlayerController.initialize().
+
## 2.6.2
* Updates Pigeon for non-nullable collection type support.
diff --git a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m
index 3ec96e7..02b9199 100644
--- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m
+++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m
@@ -790,6 +790,39 @@
XCTAssertTrue([publishedValue isKindOfClass:[FVPVideoPlayerPlugin class]]);
}
+- (void)testFailedToLoadVideoEventShouldBeAlwaysSent {
+ NSObject<FlutterPluginRegistrar> *registrar =
+ [GetPluginRegistry() registrarForPlugin:@"testFailedToLoadVideoEventShouldBeAlwaysSent"];
+ FVPVideoPlayerPlugin *videoPlayerPlugin =
+ [[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar];
+ FlutterError *error;
+
+ [videoPlayerPlugin initialize:&error];
+
+ FVPCreationOptions *create = [FVPCreationOptions makeWithAsset:nil
+ uri:@""
+ packageName:nil
+ formatHint:nil
+ httpHeaders:@{}];
+ NSNumber *textureId = [videoPlayerPlugin createWithOptions:create error:&error];
+ FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureId];
+ XCTAssertNotNil(player);
+
+ [self keyValueObservingExpectationForObject:(id)player.player.currentItem
+ keyPath:@"status"
+ expectedValue:@(AVPlayerItemStatusFailed)];
+ [self waitForExpectationsWithTimeout:10.0 handler:nil];
+
+ XCTestExpectation *failedExpectation = [self expectationWithDescription:@"failed"];
+ [player onListenWithArguments:nil
+ eventSink:^(FlutterError *event) {
+ if ([event isKindOfClass:FlutterError.class]) {
+ [failedExpectation fulfill];
+ }
+ }];
+ [self waitForExpectationsWithTimeout:10.0 handler:nil];
+}
+
#if TARGET_OS_IOS
- (void)validateTransformFixForOrientation:(UIImageOrientation)orientation {
AVAssetTrack *track = [[FakeAVAssetTrack alloc] initWithOrientation:orientation];
diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m
index 14ee7cc..88f7d37 100644
--- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m
+++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m
@@ -345,13 +345,7 @@
AVPlayerItem *item = (AVPlayerItem *)object;
switch (item.status) {
case AVPlayerItemStatusFailed:
- if (_eventSink != nil) {
- _eventSink([FlutterError
- errorWithCode:@"VideoError"
- message:[@"Failed to load video: "
- stringByAppendingString:[item.error localizedDescription]]
- details:nil]);
- }
+ [self sendFailedToLoadVideoEvent];
break;
case AVPlayerItemStatusUnknown:
break;
@@ -406,6 +400,32 @@
_displayLink.running = _isPlaying || self.waitingForFrame;
}
+- (void)sendFailedToLoadVideoEvent {
+ if (_eventSink == nil) {
+ return;
+ }
+ // Prefer more detailed error information from tracks loading.
+ NSError *error;
+ if ([self.player.currentItem.asset statusOfValueForKey:@"tracks"
+ error:&error] != AVKeyValueStatusFailed) {
+ error = self.player.currentItem.error;
+ }
+ __block NSMutableOrderedSet<NSString *> *details =
+ [NSMutableOrderedSet orderedSetWithObject:@"Failed to load video"];
+ void (^add)(NSString *) = ^(NSString *detail) {
+ if (detail != nil) {
+ [details addObject:detail];
+ }
+ };
+ NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
+ add(error.localizedDescription);
+ add(error.localizedFailureReason);
+ add(underlyingError.localizedDescription);
+ add(underlyingError.localizedFailureReason);
+ NSString *message = [details.array componentsJoinedByString:@": "];
+ _eventSink([FlutterError errorWithCode:@"VideoError" message:message details:nil]);
+}
+
- (void)setupEventSinkIfReadyToPlay {
if (_eventSink && !_isInitialized) {
AVPlayerItem *currentItem = self.player.currentItem;
@@ -587,6 +607,13 @@
// This line ensures the 'initialized' event is sent when the event
// 'AVPlayerItemStatusReadyToPlay' fires before _eventSink is set (this function
// onListenWithArguments is called)
+ // and also send error in similar case with 'AVPlayerItemStatusFailed'
+ // https://github.com/flutter/flutter/issues/151475
+ // https://github.com/flutter/flutter/issues/147707
+ if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
+ [self sendFailedToLoadVideoEvent];
+ return nil;
+ }
[self setupEventSinkIfReadyToPlay];
return nil;
}
diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml
index d309c02..31d615d 100644
--- a/packages/video_player/video_player_avfoundation/pubspec.yaml
+++ b/packages/video_player/video_player_avfoundation/pubspec.yaml
@@ -2,7 +2,7 @@
description: iOS and macOS implementation of the video_player plugin.
repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
-version: 2.6.2
+version: 2.6.3
environment:
sdk: ^3.3.0