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

Commit 6da0379

Browse files
committed
[video_player] Avoid blocking the main thread loading video count
1 parent 60982c3 commit 6da0379

File tree

2 files changed

+112
-6
lines changed

2 files changed

+112
-6
lines changed

packages/video_player/video_player/example/ios/RunnerTests/VideoPlayerTests.m

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
#import <OCMock/OCMock.h>
1010

11-
@interface FLTVideoPlayer : NSObject
11+
@interface FLTVideoPlayer : NSObject <FlutterStreamHandler>
1212
@property(readonly, nonatomic) AVPlayer *player;
1313
@end
1414

@@ -70,4 +70,89 @@ - (void)testDeregistersFromPlayer {
7070
[self waitForExpectationsWithTimeout:1 handler:nil];
7171
}
7272

73+
- (void)testVideoControls {
74+
NSObject<FlutterPluginRegistry> *registry =
75+
(NSObject<FlutterPluginRegistry> *)[[UIApplication sharedApplication] delegate];
76+
NSObject<FlutterPluginRegistrar> *registrar =
77+
[registry registrarForPlugin:@"TestVideoControls"];
78+
79+
FLTVideoPlayerPlugin *videoPlayerPlugin =
80+
(FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar];
81+
82+
NSDictionary<NSString *, id> *videoInitialization = [self testPlugin:videoPlayerPlugin uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"];
83+
XCTAssertEqualObjects(videoInitialization[@"height"], @720);
84+
XCTAssertEqualObjects(videoInitialization[@"width"], @1280);
85+
XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200);
86+
}
87+
88+
- (void)testAudioControls {
89+
NSObject<FlutterPluginRegistry> *registry =
90+
(NSObject<FlutterPluginRegistry> *)[[UIApplication sharedApplication] delegate];
91+
NSObject<FlutterPluginRegistrar> *registrar =
92+
[registry registrarForPlugin:@"TestAudioControls"];
93+
94+
FLTVideoPlayerPlugin *videoPlayerPlugin =
95+
(FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar];
96+
97+
NSDictionary<NSString *, id> *audioInitialization = [self testPlugin:videoPlayerPlugin uri:@"https://cdn.pixabay.com/audio/2021/09/06/audio_bacd4d6020.mp3"];
98+
XCTAssertEqualObjects(audioInitialization[@"height"], @0);
99+
XCTAssertEqualObjects(audioInitialization[@"width"], @0);
100+
// Perfect precision not guaranteed.
101+
XCTAssertEqualWithAccuracy([audioInitialization[@"duration"] intValue], 68500, 200);
102+
}
103+
104+
- (NSDictionary<NSString *, id> *)testPlugin:(FLTVideoPlayerPlugin *)videoPlayerPlugin uri:(NSString *)uri {
105+
FlutterError *error;
106+
[videoPlayerPlugin initialize:&error];
107+
XCTAssertNil(error);
108+
109+
FLTCreateMessage *create = [[FLTCreateMessage alloc] init];
110+
create.uri = uri;
111+
FLTTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error];
112+
113+
NSNumber *textureId = textureMessage.textureId;
114+
FLTVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureId];
115+
XCTAssertNotNil(player);
116+
117+
XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"];
118+
__block NSDictionary<NSString *, id> *initializationEvent;
119+
[player onListenWithArguments:nil eventSink:^(NSDictionary<NSString *, id> *event) {
120+
if ([event[@"event"] isEqualToString:@"initialized"]) {
121+
initializationEvent = event;
122+
XCTAssertEqual(event.count, 4);
123+
[initializedExpectation fulfill];
124+
} else {
125+
XCTFail(@"Unexpected event: %@", event);
126+
}
127+
}];
128+
[self waitForExpectationsWithTimeout:1.0 handler:nil];
129+
130+
// Starts paused.
131+
AVPlayer *avPlayer = player.player;
132+
XCTAssertEqual(avPlayer.rate, 0);
133+
XCTAssertEqual(avPlayer.volume, 1);
134+
XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusPaused);
135+
136+
// Change playback speed.
137+
FLTPlaybackSpeedMessage *playback = [[FLTPlaybackSpeedMessage alloc] init];
138+
playback.textureId = textureId;
139+
playback.speed = @2;
140+
[videoPlayerPlugin setPlaybackSpeed:playback error:&error];
141+
XCTAssertNil(error);
142+
XCTAssertEqual(avPlayer.rate, 2);
143+
XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate);
144+
145+
// Volume
146+
FLTVolumeMessage *volume = [[FLTVolumeMessage alloc] init];
147+
volume.textureId = textureId;
148+
volume.volume = @(0.1);
149+
[videoPlayerPlugin setVolume:volume error:&error];
150+
XCTAssertNil(error);
151+
XCTAssertEqual(avPlayer.volume, 0.1f);
152+
153+
[player onCancelWithArguments:nil];
154+
155+
return initializationEvent;
156+
}
157+
73158
@end

packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -331,25 +331,46 @@ - (void)updatePlayingState {
331331

332332
- (void)setupEventSinkIfReadyToPlay {
333333
if (_eventSink && !_isInitialized) {
334-
BOOL hasVideoTracks =
335-
[[self.player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo] count] != 0;
336-
CGSize size = [self.player currentItem].presentationSize;
334+
AVPlayerItem *currentItem = self.player.currentItem;
335+
CGSize size = currentItem.presentationSize;
337336
CGFloat width = size.width;
338337
CGFloat height = size.height;
339338

339+
// Wait until tracks are loaded to check duration or if there are any videos.
340+
AVAsset *asset = currentItem.asset;
341+
if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) {
342+
[asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:^{
343+
if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) {
344+
// Cancelled, or something failed.
345+
return;
346+
}
347+
// This completion block will run on an unknown AVFoundation completion queue thread.
348+
// Hop back to the main thread to set up event sink.
349+
if (!NSThread.isMainThread) {
350+
[self performSelector:_cmd onThread:NSThread.mainThread withObject:self waitUntilDone:NO];
351+
} else {
352+
[self setupEventSinkIfReadyToPlay];
353+
}
354+
}];
355+
return;
356+
}
357+
358+
BOOL hasVideoTracks = [asset tracksWithMediaType:AVMediaTypeVideo].count != 0;
359+
340360
// The player has not yet initialized when it contains video tracks.
341361
if (hasVideoTracks && height == CGSizeZero.height && width == CGSizeZero.width) {
342362
return;
343363
}
344364
// The player may be initialized but still needs to determine the duration.
345-
if ([self duration] == 0) {
365+
int64_t duration = [self duration];
366+
if (duration == 0) {
346367
return;
347368
}
348369

349370
_isInitialized = YES;
350371
_eventSink(@{
351372
@"event" : @"initialized",
352-
@"duration" : @([self duration]),
373+
@"duration" : @(duration),
353374
@"width" : @(width),
354375
@"height" : @(height)
355376
});

0 commit comments

Comments
 (0)