Resume AVPlayer video playback after app become active

HotJard picture HotJard · May 14, 2013 · Viewed 13.4k times · Source

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];
}

Answer

Lachezar Todorov picture Lachezar Todorov · Nov 9, 2015

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];
        });
    }
}