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

Commit c2102ec

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

File tree

4 files changed

+123
-7
lines changed

4 files changed

+123
-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: 90 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,93 @@ - (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+
} else {
129+
XCTFail(@"Unexpected event: %@", event);
130+
}
131+
}];
132+
[self waitForExpectationsWithTimeout:1.0 handler:nil];
133+
134+
// Starts paused.
135+
AVPlayer *avPlayer = player.player;
136+
XCTAssertEqual(avPlayer.rate, 0);
137+
XCTAssertEqual(avPlayer.volume, 1);
138+
XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusPaused);
139+
140+
// Change playback speed.
141+
FLTPlaybackSpeedMessage *playback = [[FLTPlaybackSpeedMessage alloc] init];
142+
playback.textureId = textureId;
143+
playback.speed = @2;
144+
[videoPlayerPlugin setPlaybackSpeed:playback error:&error];
145+
XCTAssertNil(error);
146+
XCTAssertEqual(avPlayer.rate, 2);
147+
XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate);
148+
149+
// Volume
150+
FLTVolumeMessage *volume = [[FLTVolumeMessage alloc] init];
151+
volume.textureId = textureId;
152+
volume.volume = @(0.1);
153+
[videoPlayerPlugin setVolume:volume error:&error];
154+
XCTAssertNil(error);
155+
XCTAssertEqual(avPlayer.volume, 0.1f);
156+
157+
[player onCancelWithArguments:nil];
158+
159+
return initializationEvent;
160+
}
161+
73162
@end

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -331,25 +331,48 @@ - (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 unknown AVFoundation completion
348+
// queue thread. 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+
[asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ]
356+
completionHandler:trackCompletionHandler];
357+
return;
358+
}
359+
360+
BOOL hasVideoTracks = [asset tracksWithMediaType:AVMediaTypeVideo].count != 0;
361+
340362
// The player has not yet initialized when it contains video tracks.
341363
if (hasVideoTracks && height == CGSizeZero.height && width == CGSizeZero.width) {
342364
return;
343365
}
344366
// The player may be initialized but still needs to determine the duration.
345-
if ([self duration] == 0) {
367+
int64_t duration = [self duration];
368+
if (duration == 0) {
346369
return;
347370
}
348371

349372
_isInitialized = YES;
350373
_eventSink(@{
351374
@"event" : @"initialized",
352-
@"duration" : @([self duration]),
375+
@"duration" : @(duration),
353376
@"width" : @(width),
354377
@"height" : @(height)
355378
});

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)