Skip to content

Commit 201ec5a

Browse files
[video_player] Simplify native iOS code (#9800)
Refactors and simplifies some of the native code in `video_player_avfoundation`: - Separates `create` into two different entry points for the two display types, instead of having one entry point and then branching several times based on the display type. - Moves `dispose` to the player instance, with the other instance-specific calls, by making plugin-level tracking cleanup part of `onDispose`. - Moves the URL->`AVPlayerItem` logic out of the two player classes and into a central helper in the plugin class, reducing duplication and simplifying the player initialization logic. - Addresses the TODO to separate texture IDs from player IDs. - Makes `expectFrame` a private implementation detail rather than making a client call it. - Removes an ARC check; we have used ARC everywhere in this repo for a long time. Part of flutter/flutter#172763 ## Pre-Review Checklist **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 1ef6b58 commit 201ec5a

File tree

16 files changed

+485
-370
lines changed

16 files changed

+485
-370
lines changed

packages/video_player/video_player_avfoundation/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.8.4
2+
3+
* Simplifies native code.
4+
15
## 2.8.3
26

37
* Removes calls to self from init and dealloc, for maintainability.

packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m

Lines changed: 75 additions & 74 deletions
Large diffs are not rendered by default.

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPTextureBasedVideoPlayer.m

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,13 @@ @interface FVPTextureBasedVideoPlayer ()
2828
// (e.g., after a seek while paused). If YES, the display link should continue to run until the next
2929
// frame is successfully provided.
3030
@property(nonatomic, assign) BOOL waitingForFrame;
31+
32+
/// Ensures that the frame updater runs until a frame is rendered, regardless of play/pause state.
33+
- (void)expectFrame;
3134
@end
3235

3336
@implementation FVPTextureBasedVideoPlayer
3437

35-
- (instancetype)initWithURL:(NSURL *)url
36-
frameUpdater:(FVPFrameUpdater *)frameUpdater
37-
displayLink:(NSObject<FVPDisplayLink> *)displayLink
38-
httpHeaders:(nonnull NSDictionary<NSString *, NSString *> *)headers
39-
avFactory:(id<FVPAVFactory>)avFactory
40-
viewProvider:(NSObject<FVPViewProvider> *)viewProvider {
41-
NSDictionary<NSString *, id> *options = nil;
42-
if ([headers count] != 0) {
43-
options = @{@"AVURLAssetHTTPHeaderFieldsKey" : headers};
44-
}
45-
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:options];
46-
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:urlAsset];
47-
return [self initWithPlayerItem:item
48-
frameUpdater:frameUpdater
49-
displayLink:displayLink
50-
avFactory:avFactory
51-
viewProvider:viewProvider];
52-
}
53-
5438
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item
5539
frameUpdater:(FVPFrameUpdater *)frameUpdater
5640
displayLink:(NSObject<FVPDisplayLink> *)displayLink
@@ -81,6 +65,10 @@ - (void)dealloc {
8165

8266
- (void)setTextureIdentifier:(int64_t)textureIdentifier {
8367
self.frameUpdater.textureIdentifier = textureIdentifier;
68+
69+
// Ensure that the first frame is drawn once available, even if the video isn't played, since
70+
// the engine is now expecting the texture to be populated.
71+
[self expectFrame];
8472
}
8573

8674
- (void)expectFrame {
@@ -118,8 +106,8 @@ - (void)seekTo:(NSInteger)position completion:(void (^)(FlutterError *_Nullable)
118106
}];
119107
}
120108

121-
- (void)dispose {
122-
[super dispose];
109+
- (void)disposeWithError:(FlutterError *_Nullable *_Nonnull)error {
110+
[super disposeWithError:error];
123111

124112
[self.playerLayer removeFromSuperlayer];
125113

@@ -214,7 +202,8 @@ - (CVPixelBufferRef)copyPixelBuffer {
214202
- (void)onTextureUnregistered:(NSObject<FlutterTexture> *)texture {
215203
dispatch_async(dispatch_get_main_queue(), ^{
216204
if (!self.disposed) {
217-
[self dispose];
205+
FlutterError *error;
206+
[self disposeWithError:&error];
218207
}
219208
});
220209
}

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,6 @@ @implementation FVPVideoPlayer {
7575
BOOL _listenersRegistered;
7676
}
7777

78-
- (instancetype)initWithURL:(NSURL *)url
79-
httpHeaders:(nonnull NSDictionary<NSString *, NSString *> *)headers
80-
avFactory:(id<FVPAVFactory>)avFactory
81-
viewProvider:(NSObject<FVPViewProvider> *)viewProvider {
82-
NSDictionary<NSString *, id> *options = nil;
83-
if ([headers count] != 0) {
84-
options = @{@"AVURLAssetHTTPHeaderFieldsKey" : headers};
85-
}
86-
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:options];
87-
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:urlAsset];
88-
return [self initWithPlayerItem:item avFactory:avFactory viewProvider:viewProvider];
89-
}
90-
9178
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item
9279
avFactory:(id<FVPAVFactory>)avFactory
9380
viewProvider:(NSObject<FVPViewProvider> *)viewProvider {
@@ -152,7 +139,7 @@ - (void)dealloc {
152139
}
153140
}
154141

155-
- (void)dispose {
142+
- (void)disposeWithError:(FlutterError *_Nullable *_Nonnull)error {
156143
// In some hot restart scenarios, dispose can be called twice, so no-op after the first time.
157144
if (_disposed) {
158145
return;

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m

Lines changed: 69 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818
// https://github.com/flutter/packages/pull/6675/#discussion_r1591210702
1919
#import "./include/video_player_avfoundation/messages.g.h"
2020

21-
#if !__has_feature(objc_arc)
22-
#error Code Requires ARC.
23-
#endif
24-
2521
/// Non-test implementation of the diplay link factory.
2622
@interface FVPDefaultDisplayLinkFactory : NSObject <FVPDisplayLinkFactory>
2723
@end
@@ -48,8 +44,7 @@ @interface FVPVideoPlayerPlugin ()
4844
@property(nonatomic, strong) id<FVPDisplayLinkFactory> displayLinkFactory;
4945
@property(nonatomic, strong) id<FVPAVFactory> avFactory;
5046
@property(nonatomic, strong) NSObject<FVPViewProvider> *viewProvider;
51-
// TODO(stuartmorgan): Decouple identifiers for platform views and texture views.
52-
@property(nonatomic, assign) int64_t nextNonTexturePlayerIdentifier;
47+
@property(nonatomic, assign) int64_t nextPlayerIdentifier;
5348
@end
5449

5550
@implementation FVPVideoPlayerPlugin
@@ -84,49 +79,39 @@ - (instancetype)initWithAVFactory:(id<FVPAVFactory>)avFactory
8479
_avFactory = avFactory ?: [[FVPDefaultAVFactory alloc] init];
8580
_viewProvider = viewProvider ?: [[FVPDefaultViewProvider alloc] initWithRegistrar:registrar];
8681
_playersByIdentifier = [NSMutableDictionary dictionaryWithCapacity:1];
87-
// Initialized to a high number to avoid collisions with texture identifiers (which are generated
88-
// separately).
89-
_nextNonTexturePlayerIdentifier = INT_MAX;
82+
_nextPlayerIdentifier = 1;
9083
return self;
9184
}
9285

9386
- (void)detachFromEngineForRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
87+
FlutterError *error;
9488
for (FVPVideoPlayer *player in self.playersByIdentifier.allValues) {
9589
// Remove the channel and texture cleanup, and the event listener, to ensure that the player
9690
// doesn't message the engine that is no longer connected.
9791
player.onDisposed = nil;
9892
player.eventListener = nil;
99-
[player dispose];
93+
[player disposeWithError:&error];
10094
}
10195
[self.playersByIdentifier removeAllObjects];
10296
SetUpFVPAVFoundationVideoPlayerApi(registrar.messenger, nil);
10397
}
10498

105-
- (int64_t)onPlayerSetup:(FVPVideoPlayer *)player {
106-
FVPTextureBasedVideoPlayer *textureBasedPlayer =
107-
[player isKindOfClass:[FVPTextureBasedVideoPlayer class]]
108-
? (FVPTextureBasedVideoPlayer *)player
109-
: nil;
110-
111-
int64_t playerIdentifier;
112-
if (textureBasedPlayer) {
113-
playerIdentifier = [self.registrar.textures registerTexture:textureBasedPlayer];
114-
[textureBasedPlayer setTextureIdentifier:playerIdentifier];
115-
} else {
116-
playerIdentifier = self.nextNonTexturePlayerIdentifier--;
117-
}
99+
- (int64_t)configurePlayer:(FVPVideoPlayer *)player
100+
withExtraDisposeHandler:(nullable void (^)(void))extraDisposeHandler {
101+
int64_t playerIdentifier = self.nextPlayerIdentifier++;
102+
self.playersByIdentifier[@(playerIdentifier)] = player;
118103

119104
NSObject<FlutterBinaryMessenger> *messenger = self.registrar.messenger;
120105
NSString *channelSuffix = [NSString stringWithFormat:@"%lld", playerIdentifier];
121106
// Set up the player-specific API handler, and its onDispose unregistration.
122107
SetUpFVPVideoPlayerInstanceApiWithSuffix(messenger, player, channelSuffix);
123108
__weak typeof(self) weakSelf = self;
124-
BOOL isTextureBased = textureBasedPlayer != nil;
125109
player.onDisposed = ^() {
126110
SetUpFVPVideoPlayerInstanceApiWithSuffix(messenger, nil, channelSuffix);
127-
if (isTextureBased) {
128-
[weakSelf.registrar.textures unregisterTexture:playerIdentifier];
111+
if (extraDisposeHandler) {
112+
extraDisposeHandler();
129113
}
114+
[weakSelf.playersByIdentifier removeObjectForKey:@(playerIdentifier)];
130115
};
131116
// Set up the event channel.
132117
FVPEventBridge *eventBridge = [[FVPEventBridge alloc]
@@ -135,12 +120,6 @@ - (int64_t)onPlayerSetup:(FVPVideoPlayer *)player {
135120
channelSuffix]];
136121
player.eventListener = eventBridge;
137122

138-
self.playersByIdentifier[@(playerIdentifier)] = player;
139-
140-
// Ensure that the first frame is drawn once available, even if the video isn't played, since
141-
// the engine is now expecting the texture to be populated.
142-
[textureBasedPlayer expectFrame];
143-
144123
return playerIdentifier;
145124
}
146125

@@ -186,55 +165,64 @@ - (void)initialize:(FlutterError *__autoreleasing *)error {
186165
upgradeAudioSessionCategory(AVAudioSessionCategoryPlayback, 0, 0);
187166
#endif
188167

189-
[self.playersByIdentifier.allValues makeObjectsPerformSelector:@selector(dispose)];
168+
FlutterError *disposeError;
169+
// Disposing a player removes it from the dictionary, so iterate over a copy.
170+
NSArray<FVPVideoPlayer *> *players = [self.playersByIdentifier.allValues copy];
171+
for (FVPVideoPlayer *player in players) {
172+
[player disposeWithError:&disposeError];
173+
}
190174
[self.playersByIdentifier removeAllObjects];
191175
}
192176

193-
- (nullable NSNumber *)createWithOptions:(nonnull FVPCreationOptions *)options
194-
error:(FlutterError **)error {
195-
BOOL textureBased = options.viewType == FVPPlatformVideoViewTypeTextureView;
196-
177+
- (nullable NSNumber *)createPlatformViewPlayerWithOptions:(nonnull FVPCreationOptions *)options
178+
error:(FlutterError **)error {
197179
@try {
198-
FVPVideoPlayer *player = textureBased ? [self texturePlayerWithOptions:options]
199-
: [self platformViewPlayerWithOptions:options];
200-
return @([self onPlayerSetup:player]);
180+
AVPlayerItem *item = [self playerItemWithCreationOptions:options];
181+
182+
// FVPVideoPlayer contains all required logic for platform views.
183+
FVPVideoPlayer *player = [[FVPVideoPlayer alloc] initWithPlayerItem:item
184+
avFactory:self.avFactory
185+
viewProvider:self.viewProvider];
186+
187+
return @([self configurePlayer:player withExtraDisposeHandler:nil]);
201188
} @catch (NSException *exception) {
202189
*error = [FlutterError errorWithCode:@"video_player" message:exception.reason details:nil];
203190
return nil;
204191
}
205192
}
206193

207-
- (nonnull FVPTextureBasedVideoPlayer *)texturePlayerWithOptions:
208-
(nonnull FVPCreationOptions *)options {
209-
FVPFrameUpdater *frameUpdater =
210-
[[FVPFrameUpdater alloc] initWithRegistry:self.registrar.textures];
211-
NSObject<FVPDisplayLink> *displayLink =
212-
[self.displayLinkFactory displayLinkWithRegistrar:_registrar
213-
callback:^() {
214-
[frameUpdater displayLinkFired];
215-
}];
216-
217-
return [[FVPTextureBasedVideoPlayer alloc] initWithURL:[NSURL URLWithString:options.uri]
218-
frameUpdater:frameUpdater
219-
displayLink:displayLink
220-
httpHeaders:options.httpHeaders
221-
avFactory:self.avFactory
222-
viewProvider:self.viewProvider];
223-
}
224-
225-
- (nonnull FVPVideoPlayer *)platformViewPlayerWithOptions:(nonnull FVPCreationOptions *)options {
226-
// FVPVideoPlayer contains all required logic for platform views.
227-
return [[FVPVideoPlayer alloc] initWithURL:[NSURL URLWithString:options.uri]
228-
httpHeaders:options.httpHeaders
229-
avFactory:self.avFactory
230-
viewProvider:self.viewProvider];
231-
}
232-
233-
- (void)disposePlayer:(NSInteger)playerIdentifier error:(FlutterError **)error {
234-
NSNumber *playerKey = @(playerIdentifier);
235-
FVPVideoPlayer *player = self.playersByIdentifier[playerKey];
236-
[self.playersByIdentifier removeObjectForKey:playerKey];
237-
[player dispose];
194+
- (nullable FVPTexturePlayerIds *)createTexturePlayerWithOptions:
195+
(nonnull FVPCreationOptions *)options
196+
error:(FlutterError **)error {
197+
@try {
198+
AVPlayerItem *item = [self playerItemWithCreationOptions:options];
199+
FVPFrameUpdater *frameUpdater =
200+
[[FVPFrameUpdater alloc] initWithRegistry:self.registrar.textures];
201+
NSObject<FVPDisplayLink> *displayLink =
202+
[self.displayLinkFactory displayLinkWithRegistrar:_registrar
203+
callback:^() {
204+
[frameUpdater displayLinkFired];
205+
}];
206+
207+
FVPTextureBasedVideoPlayer *player =
208+
[[FVPTextureBasedVideoPlayer alloc] initWithPlayerItem:item
209+
frameUpdater:frameUpdater
210+
displayLink:displayLink
211+
avFactory:self.avFactory
212+
viewProvider:self.viewProvider];
213+
214+
int64_t textureIdentifier = [self.registrar.textures registerTexture:player];
215+
[player setTextureIdentifier:textureIdentifier];
216+
__weak typeof(self) weakSelf = self;
217+
int64_t playerIdentifier = [self configurePlayer:player
218+
withExtraDisposeHandler:^() {
219+
[weakSelf.registrar.textures unregisterTexture:textureIdentifier];
220+
}];
221+
return [FVPTexturePlayerIds makeWithPlayerId:playerIdentifier textureId:textureIdentifier];
222+
} @catch (NSException *exception) {
223+
*error = [FlutterError errorWithCode:@"video_player" message:exception.reason details:nil];
224+
return nil;
225+
}
238226
}
239227

240228
- (void)setMixWithOthers:(BOOL)mixWithOthers
@@ -274,4 +262,14 @@ - (nullable NSString *)fileURLForAssetWithName:(NSString *)asset
274262
return [NSURL fileURLWithPath:path].absoluteString;
275263
}
276264

265+
/// Returns the AVPlayerItem corresponding to the given player creation options.
266+
- (nonnull AVPlayerItem *)playerItemWithCreationOptions:(nonnull FVPCreationOptions *)options {
267+
NSDictionary<NSString *, NSString *> *headers = options.httpHeaders;
268+
NSDictionary<NSString *, id> *itemOptions =
269+
headers.count == 0 ? nil : @{@"AVURLAssetHTTPHeaderFieldsKey" : headers};
270+
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:options.uri]
271+
options:itemOptions];
272+
return [AVPlayerItem playerItemWithAsset:asset];
273+
}
274+
277275
@end

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPTextureBasedVideoPlayer.h

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,17 @@ NS_ASSUME_NONNULL_BEGIN
1414
/// updates frames, and handles display link callbacks.
1515
/// If you need to display a video using platform view, use FVPVideoPlayer instead.
1616
@interface FVPTextureBasedVideoPlayer : FVPVideoPlayer <FlutterTexture>
17-
/// Initializes a new instance of FVPTextureBasedVideoPlayer with the given URL, frame updater,
18-
/// display link, HTTP headers, AV factory, and registrar.
19-
- (instancetype)initWithURL:(NSURL *)url
20-
frameUpdater:(FVPFrameUpdater *)frameUpdater
21-
displayLink:(NSObject<FVPDisplayLink> *)displayLink
22-
httpHeaders:(nonnull NSDictionary<NSString *, NSString *> *)headers
23-
avFactory:(id<FVPAVFactory>)avFactory
24-
viewProvider:(NSObject<FVPViewProvider> *)viewProvider;
17+
/// Initializes a new instance of FVPTextureBasedVideoPlayer with the given player item,
18+
/// frame updater, display link, AV factory, and view provider.
19+
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item
20+
frameUpdater:(FVPFrameUpdater *)frameUpdater
21+
displayLink:(NSObject<FVPDisplayLink> *)displayLink
22+
avFactory:(id<FVPAVFactory>)avFactory
23+
viewProvider:(NSObject<FVPViewProvider> *)viewProvider;
2524

2625
/// Sets the texture Identifier for the frame updater. This method should be called once the texture
2726
/// identifier is obtained from the texture registry.
2827
- (void)setTextureIdentifier:(int64_t)textureIdentifier;
29-
30-
/// Tells the player to run its frame updater until it receives a frame, regardless of the
31-
/// play/pause state.
32-
- (void)expectFrame;
3328
@end
3429

3530
NS_ASSUME_NONNULL_END

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,11 @@ NS_ASSUME_NONNULL_BEGIN
3636
/// A block that will be called when dispose is called.
3737
@property(nonatomic, nullable, copy) void (^onDisposed)(void);
3838

39-
/// Initializes a new instance of FVPVideoPlayer with the given URL, HTTP headers, AV factory, and
40-
/// view provider.
41-
- (instancetype)initWithURL:(NSURL *)url
42-
httpHeaders:(nonnull NSDictionary<NSString *, NSString *> *)headers
43-
avFactory:(id<FVPAVFactory>)avFactory
44-
viewProvider:(NSObject<FVPViewProvider> *)viewProvider;
45-
46-
/// Disposes the video player and releases any resources it holds.
47-
- (void)dispose;
39+
/// Initializes a new instance of FVPVideoPlayer with the given AVPlayerItem, AV factory, and view
40+
/// provider.
41+
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item
42+
avFactory:(id<FVPAVFactory>)avFactory
43+
viewProvider:(NSObject<FVPViewProvider> *)viewProvider;
4844

4945
@end
5046

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ NS_ASSUME_NONNULL_BEGIN
2525
/// Indicates whether the video player has been initialized.
2626
@property(nonatomic, readonly) BOOL isInitialized;
2727

28-
/// Initializes a new instance of FVPVideoPlayer with the given AVPlayerItem, frame updater, display
29-
/// link, AV factory, and view provider.
30-
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item
31-
avFactory:(id<FVPAVFactory>)avFactory
32-
viewProvider:(NSObject<FVPViewProvider> *)viewProvider;
33-
3428
/// Updates the playing state of the video player.
3529
- (void)updatePlayingState;
3630
@end

0 commit comments

Comments
 (0)