Skip to content

Commit 5323555

Browse files
decisivearmorclaude
andcommitted
fix: Improve iOS PiP and background playback support
- Add AVAudioSession activation for background playback - Fix PiP AVPlayerLayer reference issue for texture-based players - Add application lifecycle handling for media controls - Implement cleanupRemoteCommandCenter for proper cleanup - Add playerLayerForPiP method for subclass customization This fixes: - Background audio playback continuation - Media controls disappearing after app backgrounding - PiP not working with texture-based video players 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent bb9c5b3 commit 5323555

File tree

4 files changed

+87
-9
lines changed

4 files changed

+87
-9
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,9 @@ - (void)onTextureUnregistered:(NSObject<FlutterTexture> *)texture {
252252
});
253253
}
254254

255+
- (nullable AVPlayerLayer *)playerLayerForPiP {
256+
// Return the player layer created in initWithURL
257+
return self.playerLayer;
258+
}
259+
255260
@end

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

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem *)item
9797

9898
// Setup Remote Command Center
9999
[self setupRemoteCommandCenter];
100+
101+
#if TARGET_OS_IOS
102+
// Register for app lifecycle notifications
103+
[[NSNotificationCenter defaultCenter] addObserver:self
104+
selector:@selector(applicationWillResignActive:)
105+
name:UIApplicationWillResignActiveNotification
106+
object:nil];
107+
[[NSNotificationCenter defaultCenter] addObserver:self
108+
selector:@selector(applicationDidBecomeActive:)
109+
name:UIApplicationDidBecomeActiveNotification
110+
object:nil];
111+
#endif
100112

101113
[asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:assetCompletionHandler];
102114

@@ -532,21 +544,23 @@ - (void)setPictureInPictureEnabled:(BOOL)enabled {
532544
#if TARGET_OS_IOS
533545
if (@available(iOS 9.0, *)) {
534546
NSLog(@"setPictureInPictureEnabled called with enabled: %@", enabled ? @"YES" : @"NO");
535-
NSLog(@"Current playerLayer: %@", _playerLayer);
536-
NSLog(@"Current pipController: %@", _pipController);
537-
NSLog(@"isPictureInPictureSupported: %@", [AVPictureInPictureController isPictureInPictureSupported] ? @"YES" : @"NO");
538547

539548
if (enabled && !_pipController) {
540-
// Create AVPlayerLayer if not exists
541-
if (!_playerLayer) {
542-
NSLog(@"Creating new AVPlayerLayer");
543-
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
549+
// Get player layer from subclass or create new one
550+
AVPlayerLayer *layerForPiP = [self playerLayerForPiP];
551+
if (!layerForPiP) {
552+
NSLog(@"Creating new AVPlayerLayer for PiP");
553+
layerForPiP = [AVPlayerLayer playerLayerWithPlayer:_player];
554+
_playerLayer = layerForPiP;
544555
}
545556

557+
NSLog(@"Using playerLayer for PiP: %@", layerForPiP);
558+
NSLog(@"isPictureInPictureSupported: %@", [AVPictureInPictureController isPictureInPictureSupported] ? @"YES" : @"NO");
559+
546560
// Create PiP controller
547561
if ([AVPictureInPictureController isPictureInPictureSupported]) {
548-
NSLog(@"Creating AVPictureInPictureController with playerLayer: %@", _playerLayer);
549-
_pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:_playerLayer];
562+
NSLog(@"Creating AVPictureInPictureController with playerLayer: %@", layerForPiP);
563+
_pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:layerForPiP];
550564
_pipController.delegate = self;
551565
NSLog(@"PiP controller created: %@", _pipController);
552566
} else {
@@ -643,6 +657,37 @@ - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPict
643657
NSLog(@"Error userInfo: %@", error.userInfo);
644658
}
645659

660+
- (nullable AVPlayerLayer *)playerLayerForPiP {
661+
// Default implementation returns the instance variable
662+
// Subclasses should override this to provide their own layer
663+
return _playerLayer;
664+
}
665+
666+
#pragma mark - Application Lifecycle
667+
668+
#if TARGET_OS_IOS
669+
- (void)applicationWillResignActive:(NSNotification *)notification {
670+
NSLog(@"Application will resign active");
671+
// Ensure audio session remains active for background playback
672+
[[AVAudioSession sharedInstance] setActive:YES error:nil];
673+
674+
// Update Now Playing info to ensure it's current
675+
[self updateNowPlayingInfo];
676+
}
677+
678+
- (void)applicationDidBecomeActive:(NSNotification *)notification {
679+
NSLog(@"Application did become active");
680+
// Re-setup remote command center to ensure it's properly registered
681+
[self setupRemoteCommandCenter];
682+
683+
// Update Now Playing info
684+
[self updateNowPlayingInfo];
685+
686+
// Re-activate audio session
687+
[[AVAudioSession sharedInstance] setActive:YES error:nil];
688+
}
689+
#endif
690+
646691
#pragma mark - Remote Command Center
647692

648693
- (void)setupRemoteCommandCenter {
@@ -684,6 +729,24 @@ - (void)setupRemoteCommandCenter {
684729
#endif
685730
}
686731

732+
- (void)cleanupRemoteCommandCenter {
733+
#if TARGET_OS_IOS
734+
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
735+
736+
// Remove all command targets
737+
[commandCenter.playCommand removeTarget:self];
738+
[commandCenter.pauseCommand removeTarget:self];
739+
[commandCenter.togglePlayPauseCommand removeTarget:self];
740+
[commandCenter.changePlaybackPositionCommand removeTarget:self];
741+
742+
// Disable commands
743+
commandCenter.playCommand.enabled = NO;
744+
commandCenter.pauseCommand.enabled = NO;
745+
commandCenter.togglePlayPauseCommand.enabled = NO;
746+
commandCenter.changePlaybackPositionCommand.enabled = NO;
747+
#endif
748+
}
749+
687750
- (void)updateNowPlayingInfo {
688751
#if TARGET_OS_IOS
689752
NSMutableDictionary *nowPlayingInfo = [NSMutableDictionary dictionary];

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ - (void)initialize:(FlutterError *__autoreleasing *)error {
170170
#if TARGET_OS_IOS
171171
// Allow audio playback when the Ring/Silent switch is set to silent
172172
upgradeAudioSessionCategory(AVAudioSessionCategoryPlayback, 0, 0);
173+
174+
// Activate audio session for background playback
175+
NSError *activationError = nil;
176+
[[AVAudioSession sharedInstance] setActive:YES error:&activationError];
177+
if (activationError) {
178+
NSLog(@"Failed to activate audio session: %@", activationError);
179+
}
173180
#endif
174181

175182
[self.playersByIdentifier.allValues makeObjectsPerformSelector:@selector(dispose)];

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ NS_ASSUME_NONNULL_BEGIN
7878
artist:(nullable NSString *)artist
7979
album:(nullable NSString *)album
8080
artworkUrl:(nullable NSString *)artworkUrl;
81+
82+
/// Returns the AVPlayerLayer for PiP support. Subclasses should override if they manage their own layer.
83+
- (nullable AVPlayerLayer *)playerLayerForPiP;
8184
@end
8285

8386
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)