[video_player] Avoid blocking the main thread loading video count (#4714)

diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md
index e28ef83..fc6e28d 100644
--- a/packages/video_player/video_player/CHANGELOG.md
+++ b/packages/video_player/video_player/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.17
+
+* Avoid blocking the main thread loading video count on iOS.
+
 ## 2.2.16
 
 * Introduces `setCaptionOffset` to offset the caption display based on a Duration.
diff --git a/packages/video_player/video_player/example/ios/RunnerTests/VideoPlayerTests.m b/packages/video_player/video_player/example/ios/RunnerTests/VideoPlayerTests.m
index 90c7dc2..c57f166 100644
--- a/packages/video_player/video_player/example/ios/RunnerTests/VideoPlayerTests.m
+++ b/packages/video_player/video_player/example/ios/RunnerTests/VideoPlayerTests.m
@@ -8,7 +8,7 @@
 
 #import <OCMock/OCMock.h>
 
-@interface FLTVideoPlayer : NSObject
+@interface FLTVideoPlayer : NSObject <FlutterStreamHandler>
 @property(readonly, nonatomic) AVPlayer *player;
 @end
 
@@ -70,4 +70,91 @@
   [self waitForExpectationsWithTimeout:1 handler:nil];
 }
 
+- (void)testVideoControls {
+  NSObject<FlutterPluginRegistry> *registry =
+      (NSObject<FlutterPluginRegistry> *)[[UIApplication sharedApplication] delegate];
+  NSObject<FlutterPluginRegistrar> *registrar = [registry registrarForPlugin:@"TestVideoControls"];
+
+  FLTVideoPlayerPlugin *videoPlayerPlugin =
+      (FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar];
+
+  NSDictionary<NSString *, id> *videoInitialization =
+      [self testPlugin:videoPlayerPlugin
+                   uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"];
+  XCTAssertEqualObjects(videoInitialization[@"height"], @720);
+  XCTAssertEqualObjects(videoInitialization[@"width"], @1280);
+  XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200);
+}
+
+- (void)testAudioControls {
+  NSObject<FlutterPluginRegistry> *registry =
+      (NSObject<FlutterPluginRegistry> *)[[UIApplication sharedApplication] delegate];
+  NSObject<FlutterPluginRegistrar> *registrar = [registry registrarForPlugin:@"TestAudioControls"];
+
+  FLTVideoPlayerPlugin *videoPlayerPlugin =
+      (FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar];
+
+  NSDictionary<NSString *, id> *audioInitialization =
+      [self testPlugin:videoPlayerPlugin
+                   uri:@"https://cdn.pixabay.com/audio/2021/09/06/audio_bacd4d6020.mp3"];
+  XCTAssertEqualObjects(audioInitialization[@"height"], @0);
+  XCTAssertEqualObjects(audioInitialization[@"width"], @0);
+  // Perfect precision not guaranteed.
+  XCTAssertEqualWithAccuracy([audioInitialization[@"duration"] intValue], 68500, 200);
+}
+
+- (NSDictionary<NSString *, id> *)testPlugin:(FLTVideoPlayerPlugin *)videoPlayerPlugin
+                                         uri:(NSString *)uri {
+  FlutterError *error;
+  [videoPlayerPlugin initialize:&error];
+  XCTAssertNil(error);
+
+  FLTCreateMessage *create = [[FLTCreateMessage alloc] init];
+  create.uri = uri;
+  FLTTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error];
+
+  NSNumber *textureId = textureMessage.textureId;
+  FLTVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureId];
+  XCTAssertNotNil(player);
+
+  XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"];
+  __block NSDictionary<NSString *, id> *initializationEvent;
+  [player onListenWithArguments:nil
+                      eventSink:^(NSDictionary<NSString *, id> *event) {
+                        if ([event[@"event"] isEqualToString:@"initialized"]) {
+                          initializationEvent = event;
+                          XCTAssertEqual(event.count, 4);
+                          [initializedExpectation fulfill];
+                        }
+                      }];
+  [self waitForExpectationsWithTimeout:1.0 handler:nil];
+
+  // Starts paused.
+  AVPlayer *avPlayer = player.player;
+  XCTAssertEqual(avPlayer.rate, 0);
+  XCTAssertEqual(avPlayer.volume, 1);
+  XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusPaused);
+
+  // Change playback speed.
+  FLTPlaybackSpeedMessage *playback = [[FLTPlaybackSpeedMessage alloc] init];
+  playback.textureId = textureId;
+  playback.speed = @2;
+  [videoPlayerPlugin setPlaybackSpeed:playback error:&error];
+  XCTAssertNil(error);
+  XCTAssertEqual(avPlayer.rate, 2);
+  XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate);
+
+  // Volume
+  FLTVolumeMessage *volume = [[FLTVolumeMessage alloc] init];
+  volume.textureId = textureId;
+  volume.volume = @(0.1);
+  [videoPlayerPlugin setVolume:volume error:&error];
+  XCTAssertNil(error);
+  XCTAssertEqual(avPlayer.volume, 0.1f);
+
+  [player onCancelWithArguments:nil];
+
+  return initializationEvent;
+}
+
 @end
diff --git a/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m b/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m
index 696fba2..5d09cfe 100644
--- a/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m
+++ b/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m
@@ -331,25 +331,44 @@
 
 - (void)setupEventSinkIfReadyToPlay {
   if (_eventSink && !_isInitialized) {
-    BOOL hasVideoTracks =
-        [[self.player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo] count] != 0;
-    CGSize size = [self.player currentItem].presentationSize;
+    AVPlayerItem *currentItem = self.player.currentItem;
+    CGSize size = currentItem.presentationSize;
     CGFloat width = size.width;
     CGFloat height = size.height;
 
+    // Wait until tracks are loaded to check duration or if there are any videos.
+    AVAsset *asset = currentItem.asset;
+    if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) {
+      void (^trackCompletionHandler)(void) = ^{
+        if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) {
+          // Cancelled, or something failed.
+          return;
+        }
+        // This completion block will run on an AVFoundation background queue.
+        // Hop back to the main thread to set up event sink.
+        [self performSelector:_cmd onThread:NSThread.mainThread withObject:self waitUntilDone:NO];
+      };
+      [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ]
+                           completionHandler:trackCompletionHandler];
+      return;
+    }
+
+    BOOL hasVideoTracks = [asset tracksWithMediaType:AVMediaTypeVideo].count != 0;
+
     // The player has not yet initialized when it contains video tracks.
     if (hasVideoTracks && height == CGSizeZero.height && width == CGSizeZero.width) {
       return;
     }
     // The player may be initialized but still needs to determine the duration.
-    if ([self duration] == 0) {
+    int64_t duration = [self duration];
+    if (duration == 0) {
       return;
     }
 
     _isInitialized = YES;
     _eventSink(@{
       @"event" : @"initialized",
-      @"duration" : @([self duration]),
+      @"duration" : @(duration),
       @"width" : @(width),
       @"height" : @(height)
     });
diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml
index 63520f3..9a3697b 100644
--- a/packages/video_player/video_player/pubspec.yaml
+++ b/packages/video_player/video_player/pubspec.yaml
@@ -3,7 +3,7 @@
   widgets on Android, iOS, and web.
 repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
-version: 2.2.16
+version: 2.2.17
 
 environment:
   sdk: ">=2.14.0 <3.0.0"