Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

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

Merged
merged 4 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/video_player/video_player/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#import <OCMock/OCMock.h>

@interface FLTVideoPlayer : NSObject
@interface FLTVideoPlayer : NSObject <FlutterStreamHandler>
@property(readonly, nonatomic) AVPlayer *player;
@end

Expand Down Expand Up @@ -70,4 +70,91 @@ - (void)testDeregistersFromPlayer {
[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
Original file line number Diff line number Diff line change
Expand Up @@ -331,25 +331,44 @@ - (void)updatePlayingState {

- (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)
});
Expand Down
2 changes: 1 addition & 1 deletion packages/video_player/video_player/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter
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"
Expand Down