在 iOS 中循环播放视频会导致 pause/flash(MPMoviePlayerController 和 AVPlayer)

Looping video in iOS cause a small pause/flash (MPMoviePlayerController & AVPlayer)

我真的对我的欢迎视图控制器感到疯狂。

我有一个连续循环的背景视频,但我使用的每个解决方案都会在视频完成和循环时产生一个小 pause/flash。

我使用了两个解决方案:来自 AVFoundation 的 MPMoviePlayerControllerAVPlayer 但我得到了相同的结果,视频循环播放时有一个小的白色闪光重播。

我的 MPMoviePlayerController 解决方案(我更喜欢这个解决方案)

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSURL *videoURL = [[NSBundle mainBundle] URLForResource:@"welcome_video" withExtension:@"mp4"];

    self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:videoURL];

    self.moviePlayer.controlStyle = MPMovieControlStyleNone;
    self.moviePlayer.scalingMode = MPMovieScalingModeAspectFill;

    self.moviePlayer.view.frame = self.view.frame;
    [self.view insertSubview:self.moviePlayer.view atIndex:0];

    [self.moviePlayer prepareToPlay];

    // Loop video
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loopVideo) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
}

- (void)loopVideo
{
    [self.moviePlayer play];
}

我的 AVPlayer 解决方案

 (void)viewDidLoad
{
    [super viewDidLoad];

    NSError *sessionError = nil;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&sessionError];
    [[AVAudioSession sharedInstance] setActive:YES error:&sessionError];

    //Set up player
    NSURL *movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"welcome_video" ofType:@"mp4"]];
    AVAsset *avAsset = [AVAsset assetWithURL:movieURL];
    AVPlayerItem *avPlayerItem =[[AVPlayerItem alloc]initWithAsset:avAsset];
    self.avplayer = [[AVPlayer alloc]initWithPlayerItem:avPlayerItem];
    AVPlayerLayer *avPlayerLayer =[AVPlayerLayer playerLayerWithPlayer:self.avplayer];
    [avPlayerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [avPlayerLayer setFrame:[[UIScreen mainScreen] bounds]];
    [self.movieView.layer addSublayer:avPlayerLayer];

    //Config player
    [self.avplayer seekToTime:kCMTimeZero];
    [self.avplayer setVolume:0.0f];
    [self.avplayer setActionAtItemEnd:AVPlayerActionAtItemEndNone];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playerItemDidReachEnd:)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:[self.avplayer currentItem]];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playerStartPlaying)
                                                 name:UIApplicationDidBecomeActiveNotification object:nil];

}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.avplayer pause];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self.avplayer play];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}

- (void)playerItemDidReachEnd:(NSNotification *)notification 
{
    AVPlayerItem *p = [notification object];
    [p seekToTime:kCMTimeZero];
}

- (void)playerStartPlaying
{
    [self.avplayer play];
}

这些实现有什么问题?我确实尝试了在此站点上找到的不同修复程序,但似乎没有任何效果。

有什么建议吗?

嗯..我可能对 AVPlayer-方法有想法。

AVPlayer有这个方法:

- (void)setRate:(float)rate
           time:(CMTime)itemTime
     atHostTime:(CMTime)hostClockTime

这意味着您可以指定希望 AVPlayerkCMTimeZero 开始的时间。我没有实际使用过它,但我知道它是如何工作的。

您需要准确知道第一次开始播放视频的时间。我看到您有自己的 -(void)playerStartPlaying,它由 didBecomeActive 上的 notificationCenter 调用。我建议将此方法用于 [player play]; 的应用程序变体,因此将

[self playerStartPlaying];

viewDidAppear 而不是 [self.avplayer play];。它可能已经足够好了。

如果您在这里设法找到设备的 hostClockTime,并添加视频的长度,您应该会得到您希望它从头开始的确切时间。我没有测试任何这些,我是在头脑中打字,所以你需要理解我在做什么,然后自己修复它。

- (void)playerStartPlaying
{
    //The device's hostClockTime. Basically a number indicating how long the device has been powered on.
    CMTime hostClockTime = CMClockGetHostTimeClock;

    //A CMTime indicating when you want the video to play the next time.
    CMTime nextPlay = CMTimeAdd(hostClockTime, self.avplayer.currentItem.duration);
    /* I don't know if that was correct or not, but you'll find out */

    //Start playing if we're not already playing. There might be an avplayer.isPlaying or something, I don't know, this is probably working as well..
    if(self.avplayer.rate != 1.0)
        [self.avplayer play];

    //Tell the player to restart the video at the correct time.
    [self.avplayer setRate:1.0 time:kCMTimeZero atHostTime:nextPlay];
}

您必须删除整个 AVPlayerItemDidPlayToEndTimeNotification-东西。当视频播放到结尾时,为时已晚。我们现在正在做的是告诉它什么时候开始第二轮什么时候开始第一轮。当 didPlayToEndTime 被触发时,我们不希望发生任何事情,我们正在手动处理它。

所以,如果您了解我上面所做的,您还会注意到该视频只会播放两次。我们告诉视频播放,同时我们告诉它在 time = now+videoLength 重播。当 that 重播完成后,什么也没有发生。它只是到达终点。要解决此问题,您需要在 AVPlayer 上执行 setRate:time:atHostTime 的同时以某种方式调用 -(void)playerStartPlaying。我猜你可以启动一个 NSTimerdispatch_time 并让它在恰好 nextPlay 的时间内执行他的方法,但这有点违背了这个东西的目的。也许不吧。你可以尝试不同的东西。你可能可以成功地做到这一点,但我建议找到一种方法来注册玩家从一开始就开始的时间。也许你可以观察rate什么的,我不知道..如果你想用延迟的方法试试,你可以试试这个:

double delayInSeconds = CMTimeGetSeconds(self.avplayer.currentItem.duration); //or something
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self playerStartPlaying];
});

请记住,这是一些递归的狗屎,所以即使你暂停视频,它仍然会在这段时间后继续调用。在那种情况下,我建议制作一个 BOOL paused; 并取消整个 playerStartPlaying 的执行(如果它设置为 YES)。 ..当然,只要你想暂停,就在你说 [player pause];

的旁边设置 paused = YES;

如果这确实有效,但您仍然会出现闪光,我知道有几种方法可以改善这一点。例如,CMTimeAdd() 可能应该使用某种同步工具来确保时间加起来使用正确的 timeScale

我现在花了太多时间写这篇文章,它甚至可能行不通。我不知道。祝你好运,晚安。