-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[video_player] ios picture in picture #6284
Changes from 1 commit
42ffe17
08f723d
e7921c2
d319e04
b54f049
4a34420
826b995
8e15600
78be5cf
5d91352
5f7281a
c38e3d3
f0e4820
567e146
77287c1
5ca97ca
6309b2f
db3eb7d
3a4d91e
f8a3ed6
3328e52
17a580e
3cbfd50
a9317da
6572321
82912b4
e4582ec
2e2c31d
63b5bd8
5c0da92
071893d
fd0e9cd
2af7230
f2ee1a4
1047384
f9ec3f5
5927554
620c179
de5d58a
5fd2bcb
3cc7213
ff67016
f34b8b4
0c2d632
16b2c0f
bab27ed
a829a14
85c77e7
640db39
ed675e3
7eddf97
0bc160a
db8a9b2
699ea6f
2a217d5
e621157
31b6a9f
0ba1b21
c45a2c2
f188b5f
892aac0
c841ad4
36333f6
d07cf9c
abe022b
66b5ff1
467b85e
e9bdfd5
faed7b8
3fb924b
90f993a
a901313
83edcef
2b8b565
c6f81b7
6f66373
cc639b2
23defa3
eaaaac2
de162e8
326c187
96c788a
565012b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,8 +4,8 @@ | |
|
|
||
| #import "FLTVideoPlayerPlugin.h" | ||
| #import <AVFoundation/AVFoundation.h> | ||
| #import <GLKit/GLKit.h> | ||
| #import <AVKit/AVKit.h> | ||
| #import <GLKit/GLKit.h> | ||
| #import "AVAssetTrackUtils.h" | ||
| #import "messages.g.h" | ||
|
|
||
|
|
@@ -32,7 +32,8 @@ - (void)onDisplayLink:(CADisplayLink *)link { | |
| } | ||
| @end | ||
|
|
||
| @interface FLTVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler, AVPictureInPictureControllerDelegate> | ||
| @interface FLTVideoPlayer | ||
| : NSObject <FlutterTexture, FlutterStreamHandler, AVPictureInPictureControllerDelegate> | ||
| @property(readonly, nonatomic) AVPlayer *player; | ||
| @property(readonly, nonatomic) AVPlayerItemVideoOutput *videoOutput; | ||
| @property(readonly, nonatomic) CADisplayLink *displayLink; | ||
|
|
@@ -43,7 +44,7 @@ @interface FLTVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler, AVPi | |
| @property(nonatomic, readonly) BOOL isPlaying; | ||
| @property(nonatomic) BOOL isLooping; | ||
| @property(nonatomic, readonly) BOOL isInitialized; | ||
| @property(nonatomic) AVPlayerLayer* playerLayer; | ||
| @property(nonatomic) AVPlayerLayer *playerLayer; | ||
| @property(nonatomic) bool pictureInPictureEnabled; | ||
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - (instancetype)initWithURL:(NSURL *)url | ||
| frameUpdater:(FLTFrameUpdater *)frameUpdater | ||
|
|
@@ -62,7 +63,6 @@ - (instancetype)initWithURL:(NSURL *)url | |
| AVPictureInPictureController *_pipController; | ||
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #endif | ||
|
|
||
|
|
||
| @implementation FLTVideoPlayer | ||
| - (instancetype)initWithAsset:(NSString *)asset frameUpdater:(FLTFrameUpdater *)frameUpdater { | ||
| NSString *path = [[NSBundle mainBundle] pathForResource:asset ofType:nil]; | ||
|
|
@@ -243,24 +243,26 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem *)item | |
| } | ||
|
|
||
| - (void)setPictureInPicture:(BOOL)pictureInPictureEnabled { | ||
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (self.pictureInPictureEnabled == pictureInPictureEnabled) { | ||
| return; | ||
| } | ||
| if (self.pictureInPictureEnabled == pictureInPictureEnabled) { | ||
| return; | ||
| } | ||
|
|
||
| self.pictureInPictureEnabled = pictureInPictureEnabled; | ||
| if (_pipController && self.pictureInPictureEnabled && ![_pipController isPictureInPictureActive]) { | ||
| self.playerLayer.opacity = 0.001; | ||
| if (_eventSink != nil) { | ||
| _eventSink(@{@"event" : @"startingPiP"}); | ||
| } | ||
| dispatch_async(dispatch_get_main_queue(), ^{ | ||
| [_pipController startPictureInPicture]; | ||
| }); | ||
| } else if (_pipController && !self.pictureInPictureEnabled && [_pipController isPictureInPictureActive]) { | ||
| dispatch_async(dispatch_get_main_queue(), ^{ | ||
| [_pipController stopPictureInPicture]; | ||
| }); | ||
| self.pictureInPictureEnabled = pictureInPictureEnabled; | ||
| if (_pipController && self.pictureInPictureEnabled && | ||
| ![_pipController isPictureInPictureActive]) { | ||
| self.playerLayer.opacity = 0.001; | ||
| if (_eventSink != nil) { | ||
| _eventSink(@{@"event" : @"startingPiP"}); | ||
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| dispatch_async(dispatch_get_main_queue(), ^{ | ||
| [_pipController startPictureInPicture]; | ||
| }); | ||
| } else if (_pipController && !self.pictureInPictureEnabled && | ||
| [_pipController isPictureInPictureActive]) { | ||
| dispatch_async(dispatch_get_main_queue(), ^{ | ||
| [_pipController stopPictureInPicture]; | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| #if TARGET_OS_IOS | ||
|
|
@@ -271,50 +273,56 @@ - (void)setupPipController { | |
| } | ||
vanlooverenkoen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| - (void)usePlayerLayer: (CGRect) frame { | ||
| if (_player) { | ||
| self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; | ||
| UIViewController* vc = [[[UIApplication sharedApplication] keyWindow] rootViewController]; | ||
| self.playerLayer.frame = frame; | ||
| self.playerLayer.needsDisplayOnBoundsChange = YES; | ||
| self.playerLayer.opacity = 0; | ||
| [vc.view.layer addSublayer:self.playerLayer]; | ||
| vc.view.layer.needsDisplayOnBoundsChange = YES; | ||
| #if TARGET_OS_IOS | ||
| [self setupPipController]; | ||
| #endif | ||
| } | ||
| - (void)usePlayerLayer:(CGRect)frame { | ||
| if (_player) { | ||
| self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; | ||
| UIViewController *vc = [[[UIApplication sharedApplication] keyWindow] rootViewController]; | ||
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| self.playerLayer.frame = frame; | ||
| self.playerLayer.needsDisplayOnBoundsChange = YES; | ||
| self.playerLayer.opacity = 0; | ||
| [vc.view.layer addSublayer:self.playerLayer]; | ||
| vc.view.layer.needsDisplayOnBoundsChange = YES; | ||
| #if TARGET_OS_IOS | ||
| [self setupPipController]; | ||
| #endif | ||
| } | ||
| } | ||
| #endif | ||
|
|
||
vanlooverenkoen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #if TARGET_OS_IOS | ||
| - (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController { | ||
| self.pictureInPictureEnabled = false; | ||
| self.playerLayer.opacity = 0; | ||
| if (_eventSink != nil) { | ||
| _eventSink(@{@"event" : @"stoppedPiP"}); | ||
| } | ||
| - (void)pictureInPictureControllerDidStopPictureInPicture: | ||
| (AVPictureInPictureController *)pictureInPictureController { | ||
| self.pictureInPictureEnabled = false; | ||
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| self.playerLayer.opacity = 0; | ||
| if (_eventSink != nil) { | ||
| _eventSink(@{@"event" : @"stoppedPiP"}); | ||
| } | ||
| } | ||
|
|
||
| - (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController { | ||
| self.playerLayer.opacity = 0.001; | ||
| if (_eventSink != nil) { | ||
| _eventSink(@{@"event" : @"startingPiP"}); | ||
| } | ||
| [self updatePlayingState]; | ||
| - (void)pictureInPictureControllerDidStartPictureInPicture: | ||
| (AVPictureInPictureController *)pictureInPictureController { | ||
| self.playerLayer.opacity = 0.001; | ||
| if (_eventSink != nil) { | ||
| _eventSink(@{@"event" : @"startingPiP"}); | ||
| } | ||
| [self updatePlayingState]; | ||
| } | ||
|
|
||
| - (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController { | ||
| - (void)pictureInPictureControllerWillStopPictureInPicture: | ||
| (AVPictureInPictureController *)pictureInPictureController { | ||
| } | ||
|
|
||
| - (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController { | ||
| - (void)pictureInPictureControllerWillStartPictureInPicture: | ||
| (AVPictureInPictureController *)pictureInPictureController { | ||
| } | ||
|
|
||
| - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error { | ||
|
|
||
| - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController | ||
| failedToStartPictureInPictureWithError:(NSError *)error { | ||
| } | ||
|
|
||
| - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler { | ||
| - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController | ||
| restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: | ||
| (void (^)(BOOL))completionHandler { | ||
| } | ||
| #endif | ||
|
|
||
|
|
@@ -716,20 +724,21 @@ - (void)setMixWithOthers:(FLTMixWithOthersMessage *)input | |
| } | ||
| } | ||
|
|
||
|
|
||
| - (void)preparePictureInPicture:(FLTPreparePictureInPictureMessage*)input error:(FlutterError**)error { | ||
| FLTVideoPlayer* player = self.playersByTextureId[input.textureId]; | ||
| [player usePlayerLayer:CGRectMake(input.left.floatValue, input.top.floatValue, | ||
| - (void)preparePictureInPicture:(FLTPreparePictureInPictureMessage *)input | ||
| error:(FlutterError **)error { | ||
| FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recommend checking to see if
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Populating the output
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stuartmorgan is there an example somewhere on how I do that?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like: - (BOOL)doesInfoPlistSupportPictureInPicture:(FlutterError **)error {
NSArray *backgroundModes = [NSBundle.mainBundle objectForInfoDictionaryKey:@"UIBackgroundModes"];
if (![backgroundModes isKindOfClass:[NSArray class]] ||
![backgroundModes containsObject:@"audio"]) {
*error = [FlutterError errorWithCode:@"video_player"
message:@"missing audio UIBackgroundModes audio in Info.plist"
details:nil];
return NO;
}
return YES;
}
- (void)startPictureInPicture:(FLTStartPictureInPictureMessage *)input
error:(FlutterError **)error {
if (![self doesInfoPlistSupportPictureInPicture:error]) {
return;
}
FLTVideoPlayer *player = self.playersByTextureId[input.textureId];
[player startOrStopPictureInPicture:YES];
}
- (void)stopPictureInPicture:(FLTStopPictureInPictureMessage *)input error:(FlutterError **)error {
if (![self doesInfoPlistSupportPictureInPicture:error]) {
return;
}
FLTVideoPlayer *player = self.playersByTextureId[input.textureId];
[player startOrStopPictureInPicture:NO];
} |
||
| [player usePlayerLayer:CGRectMake(input.left.floatValue, input.top.floatValue, | ||
| input.width.floatValue, input.height.floatValue)]; | ||
| } | ||
|
|
||
| - (void)setPictureInPicture:(FLTPictureInPictureMessage*)input error:(FlutterError**)error { | ||
| FLTVideoPlayer* player = self.playersByTextureId[input.textureId]; | ||
| [player setPictureInPicture:input.enabled.intValue == 1]; | ||
| - (void)setPictureInPicture:(FLTPictureInPictureMessage *)input error:(FlutterError **)error { | ||
| FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; | ||
| [player setPictureInPicture:input.enabled.intValue == 1]; | ||
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| - (nullable NSNumber *)isPictureInPictureSupported:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { | ||
| return [NSNumber numberWithBool:[AVPictureInPictureController isPictureInPictureSupported]]; | ||
| - (nullable NSNumber *)isPictureInPictureSupported: | ||
| (FlutterError *_Nullable __autoreleasing *_Nonnull)error { | ||
vanlooverenkoen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return [NSNumber numberWithBool:[AVPictureInPictureController isPictureInPictureSupported]]; | ||
| } | ||
|
|
||
| @end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails on the iPhone simulator since it's not supported, and that's what runs in CI. Interestingly, it is supported on the iPad Pro simulator, I confirmed this test passes there. https://stackoverflow.com/questions/68939003/is-picture-in-picture-possible-on-ios-simulator/72326067#comment122853678_68939003
Recommend something like:
@stuartmorgan these tests may be worth running on the iPad simulator as well, since the behavior is different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have already added the recommended change. Please let me know what I should do to get iPad tests as well