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 3 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,48 @@ - (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 unknown AVFoundation completion
// queue thread. Hop back to the main thread to set up event sink.
if (!NSThread.isMainThread) {
[self performSelector:_cmd onThread:NSThread.mainThread withObject:self waitUntilDone:NO];
} else {
[self setupEventSinkIfReadyToPlay];
}
};
[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