[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