Skip to content

Commit a5b97ea

Browse files
authored
[video_player] Avoid blocking the main thread loading video count (flutter#4714)
1 parent 72dff1b commit a5b97ea

File tree

4 files changed

+117
-7
lines changed

4 files changed

+117
-7
lines changed

packages/video_player/video_player/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.2.17
2+
3+
* Avoid blocking the main thread loading video count on iOS.
4+
15
## 2.2.16
26

37
* Introduces `setCaptionOffset` to offset the caption display based on a Duration.

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

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

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -331,25 +331,44 @@ - (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+
void (^trackCompletionHandler)(void) = ^{
343+
if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) {
344+
// Cancelled, or something failed.
345+
return;
346+
}
347+
// This completion block will run on an AVFoundation background queue.
348+
// Hop back to the main thread to set up event sink.
349+
[self performSelector:_cmd onThread:NSThread.mainThread withObject:self waitUntilDone:NO];
350+
};
351+
[asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ]
352+
completionHandler:trackCompletionHandler];
353+
return;
354+
}
355+
356+
BOOL hasVideoTracks = [asset tracksWithMediaType:AVMediaTypeVideo].count != 0;
357+
340358
// The player has not yet initialized when it contains video tracks.
341359
if (hasVideoTracks && height == CGSizeZero.height && width == CGSizeZero.width) {
342360
return;
343361
}
344362
// The player may be initialized but still needs to determine the duration.
345-
if ([self duration] == 0) {
363+
int64_t duration = [self duration];
364+
if (duration == 0) {
346365
return;
347366
}
348367

349368
_isInitialized = YES;
350369
_eventSink(@{
351370
@"event" : @"initialized",
352-
@"duration" : @([self duration]),
371+
@"duration" : @(duration),
353372
@"width" : @(width),
354373
@"height" : @(height)
355374
});

packages/video_player/video_player/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter
33
widgets on Android, iOS, and web.
44
repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player
55
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
6-
version: 2.2.16
6+
version: 2.2.17
77

88
environment:
99
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)