diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 84579777521..fe47d79e6a6 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 3ec96e78538..02b9199a74f 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 @@ - (void)testPublishesInRegistration { XCTAssertTrue([publishedValue isKindOfClass:[FVPVideoPlayerPlugin class]]); } +- (void)testFailedToLoadVideoEventShouldBeAlwaysSent { + NSObject *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 14ee7ccefde..88f7d3722f8 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 @@ - (void)observeValueForKeyPath:(NSString *)path 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 @@ - (void)updatePlayingState { _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 *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 @@ - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments // 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 d309c02258d..31d615dd886 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_avfoundation 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