Resume AVPlayer video playback after app become active
Asked Answered
C

5

16

I write custom player from AVPlayer for video playback. According to Apple docs set the video layer:

    self.player = [IPLPlayer new];
    self.player.playerLayer = (AVPlayerLayer *)self.playerView.layer;

Where self.playerView is usual class from those docs:

    @implementation PlayerView

+ (Class) layerClass {
    return [AVPlayerLayer class];
}

- (AVPlayer *)player {
    return [(AVPlayerLayer *)[self layer] player];
}

- (void)setPlayer:(AVPlayer *) player {
    [(AVPlayerLayer *) [self layer] setPlayer:player];
}

The problem is: When close app (Home button), or block screen, the video playback is stopped, and when resume ONLY audio playback resumed, the image on screen is still those was before block screen - it's fully static and note change frames.

How to resume VIDEO playing after screen is blocked?

Seems I must to register notifications, and after app become active resume video layer:

    -(void)registerNotification
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(willEnterBackground)
                                                 name:UIApplicationWillResignActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didEnterForeground)
                                                 name:UIApplicationDidBecomeActiveNotification object:nil];
}

-(void)unregisterNotification
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


-(void)willEnterBackground
{
    NSLog(@"willEnterBackground");
    [self.playerView willEnterBackground];
}

-(void)didEnterForeground
{
    NSLog(@"didEnterForeground");
    [self.playerView didEnterForeground];
}
Camm answered 14/5, 2013 at 17:8 Comment(0)
K
19

And one solution that binds all this information together.

Maybe player status should be handled differently, but I like the recursive way.

Note: If you do not need the exact seek time, you can use [_player seekToTime:<#(CMTime)#> completionHandler:<#^(BOOL finished)completionHandler#>] It's faster but it seeks to the nearest key frame.

- (void)viewDidLoad
{
    [super viewDidLoad];

....

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnteredForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnteredBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
}

-(void)viewWillDisappear:(BOOL)animated
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}

....

-(void) appEnteredForeground {
    AVPlayerLayer *player = (AVPlayerLayer *)[playerView layer];
    [player setPlayer:NULL];
    [player setPlayer:_player];
    [self playAt:currentTime];
}

-(void) appEnteredBackground {
    [_player pause];
    currentTime = [_player currentTime];
}

-(void)playAt: (CMTime)time {
    if(_player.status == AVPlayerStatusReadyToPlay && _player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
        [_player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
            [_player play];
        }];
    } else {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self playAt:time];
        });
    }
}
Kylander answered 9/11, 2015 at 16:11 Comment(0)
T
8

This works for me on Swift 3

Add somewhere while setting up the view:

NotificationCenter.default.addObserver(self, 
  selector: #selector(appWillEnterForegroundNotification),
  name: .UIApplicationWillEnterForeground, object: nil)

Grab your player and force it to play:

func appWillEnterForegroundNotification() {
  myPlayer.play()
}

Don't forget to remove the observers when you don't need them:

override func viewWillDisappear(_ animated: Bool) {
  super.viewWillDisappear(animated)
  NotificationCenter.default.removeObserver(self)
}
Taro answered 21/4, 2017 at 9:2 Comment(5)
Hmm.. I'm pretty sure this will cause a retain +1 to the class that contains it. So 'deinit' will never be called. You would have to remove the observer from somewhere else so that the object does get released :]Telfore
Yeah viewDidDisappear would be a good place to remove the observer.Deductive
I've updated the answer. Thank you @AndresCanella and Harish <3Taro
@AndresCanella could you please elaborate why this would create additional retain? NSNotificationCenter does not retain observers.Cecilius
@Cecilius At the moment of this posting, in practice, it was retaining. It seems at some point it was strong referenced. Having removeObserver in dealloc caused the containing object never to release as dealloc was never called. Moving it to viewWillDisappear fixed the issue. I've not tested it since.Telfore
P
3

Use the UIApplicationWillEnterForegroundNotification as well. That way you know your app will be active and visible to the user:

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(appEnteredForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
Phillane answered 14/5, 2013 at 23:44 Comment(1)
Sorry, but my question wasn't about it.Camm
D
3

The trick is to detach all video layers from their players when the app did enter background and reattaching them when the app did become active again.

So in your -applicationDidEnterBackground: you got to trigger a mechanism that results in

avPlayerLayer.player = nil;

and in your -applicationDidBecomeActive: you reattach the player like

avPlayerLayer.player = self.avPlayer;

Also have a look at this Tech Note (QA1668) from Apple.

Disrate answered 2/4, 2014 at 17:50 Comment(1)
Didn't have to do it, maybe it is fixed?Aurore
C
1

After some research I've found, that the same bag is in iOS player (WebBrowser, MPMoviePlayerController). May be because distribution type of content is Progressive Download.

So solution is:

  • Use below notifications and save current time.
  • After app is resumed, recreate AVPlayer and start playing from saved time.

In all other cases image is blocked, or view become black.

Camm answered 17/5, 2013 at 17:18 Comment(1)
This should be the accepted answer. Also if you implement behaviour on notifications be aware that you CANNOT pass the player object to UIApplication notifications, if your notification is never called this is the culpritAurore

© 2022 - 2024 — McMap. All rights reserved.