Resume AVPlayer video playback after app become active

浪尽此生 提交于 2019-12-20 10:27:06

问题


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

回答1:


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



回答2:


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



回答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];



回答4:


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.




回答5:


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.



来源:https://stackoverflow.com/questions/16548963/resume-avplayer-video-playback-after-app-become-active

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!