在AVPlayer中切换video时更改时会创建Flash

我正在使用AVFoundation的AVPlayer播放由1个较长video制作的2个video片段(因此第一个video片段的结尾与第二个片段的开头相匹配)

当第一个video结束并且用户点击时,我创建一个新的AVPlayer并将其分配给我的PlayerView,并开始播放第二个剪辑。

这一切都有效,然而,有一个突出的屏幕“闪烁”。

我的假设是,这是由玩家视图删除第一个剪辑然后显示第二个剪辑引起的。

我需要的是没有出现这种闪烁,因此在两个剪辑之间进行无缝连接。

有没有人知道是否有办法通过AVPlayer *类来阻止这个flickr,或者通过做某事来“伪造”它以使其不可见。

谢谢

下面是我的加载和播放方法的代码:

- (void)loadAssetFromFile { NSURL *fileURL = nil; switch (playingClip) { case 1: fileURL = [[NSBundle mainBundle] URLForResource:@"wh_3a" withExtension:@"mp4"]; break; case 2: fileURL = [[NSBundle mainBundle] URLForResource:@"wh_3b" withExtension:@"mp4"]; break; case 3: fileURL = [[NSBundle mainBundle] URLForResource:@"wh_3c" withExtension:@"mp4"]; break; case 4: fileURL = [[NSBundle mainBundle] URLForResource:@"wh_3d" withExtension:@"mp4"]; break; default: return; break; } AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil]; NSString *tracksKey = @"tracks"; [asset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:tracksKey] completionHandler: ^{ // The completion block goes here. NSError *error = nil; AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error]; if (status == AVKeyValueStatusLoaded) { self.playerItem = [AVPlayerItem playerItemWithAsset:asset]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; self.player = [AVPlayer playerWithPlayerItem:playerItem]; [playerView setPlayer:player]; [self.player seekToTime:kCMTimeZero]; [self play]; } else { // Deal with the error appropriately. NSLog(@"The asset's tracks were not loaded:\n%@", [error localizedDescription]); } }]; } 

您无需为此任务重新创建AVPlayer 。 您可以拥有多个AVPlayerItem ,然后通过[AVPlayer replaceCurrentItemWithPlayerItem:item]切换哪一个是最新的。

此外,您可以使用以下代码观察当前项目的更改时间。

  static void* CurrentItemObservationContext = &CurrentItemObservationContext; 

…创建播放器后,注册观察者:

  [player1 addObserver:self forKeyPath:kCurrentItemKey options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:CurrentItemObservationContext]; 

  - (void)observeValueForKeyPath:(NSString*) path ofObject:(id)object change:(NSDictionary*)change context:(void*)context { if (context == CurrentItemObservationContext) { AVPlayerItem *item = [change objectForKey:NSKeyValueChangeNewKey]; if (item != (id)[NSNull null]) { [player1 play]; } } } 

我找到了两种解决方法。 对我来说,这两种方法都有效,我更喜欢第二种方法。

首先,正如@Alex Kennberg所提到的,创建两套AVPlayerLayerAVPlayer 。 在video之间切换时切换它们。 务必设置背景颜色以清除颜色。

其次,使用UIImageView作为AVPlayerLayer的所有者视图。 在切换video之前,创建video的缩略图图像并将其设置为imageview。 务必正确设置视图模式。

我试过这个,它对我有用。

  if (layerView1.playerLayer.superlayer) { [layerView1.playerLayer removeFromSuperlayer]; } 

但我也在分配自己的AVPlayerLayer而不是使用IB来完成它。

经过太多努力没有成功,我终于找到了一个解决方案,而不是最好的解决方案,但是有效

我的输入代码如下,请看一下loadVideo方法

 #import "ViewController.h" #import  @interface ViewController () @property (nonatomic, strong) NSArray *videos; @property (nonatomic, assign) NSInteger videoIndex; @property (nonatomic, strong) AVPlayer *player; @property (nonatomic, strong) AVPlayerLayer *playerLayer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; self.videos = @[@"1.mp4", @"2.mp4", @"3.mp4", @"4.mp4", @"5.mp4", @"6.mp4"]; self.videoIndex = 0; [self loadVideo]; [self configureGestures:self.view]; //changes video on swipe } -(void)prevZoomPanel{ if(self.videoIndex <= 0){ NSLog(@"cant go prev"); return; } self.videoIndex -= 1; [self loadVideo]; } -(void)nextZoomPanel{ if(self.videoIndex >= self.videos.count - 1){ NSLog(@"cant go next"); return; } self.videoIndex += 1; [self loadVideo]; } #pragma mark - Load Video -(void)loadVideo{ NSURL * bundle = [[NSBundle mainBundle] bundleURL]; NSURL * file = [NSURL URLWithString:self.videos[self.videoIndex] relativeToURL:bundle]; NSURL * absoluteFile = [file absoluteURL]; AVPlayerItem *item = [AVPlayerItem playerItemWithURL:absoluteFile]; //************* //DO NOT USE '[self.player replaceCurrentItemWithPlayerItem:item]', it flashes, instead, initialize the instace again. //Why is replaceCurrentItemWithPlayerItem flashing but playerWithPlayerItem is NOT? // if you want to see the diferente, uncomment the code above self.player = [AVPlayer playerWithPlayerItem:item]; // if (self.player == nil) { // self.player = [AVPlayer playerWithPlayerItem:item]; // }else{ // [self.player replaceCurrentItemWithPlayerItem:item]; // } //************* //create an instance of AVPlayerLayer and add it on self.view //afraid of this AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; playerLayer.videoGravity = AVLayerVideoGravityResizeAspect; playerLayer.frame = self.view.layer.bounds; [self.view.layer addSublayer:playerLayer]; //************* //play the video before remove the old AVPlayerLayer instance, at this time will have 2 sublayers [self.player play]; NSLog(@"sublayers before: %zd", self.view.layer.sublayers.count); //************* //remove all sublayers after 0.09s, to avoid the flash, 0.08 still flashing. //TODO: tested on iPhone X, need to test on slower iPhones to check if the time is enough. //Why do I need to wait to remove? Is that safe? What if I swipe a lot too fast, faster than 0.09s ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.09 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSArray* sublayers = [NSArray arrayWithArray:self.view.layer.sublayers]; NSInteger idx = 0; for (CALayer *layer in sublayers) { if (idx < self.view.layer.sublayers.count && self.view.layer.sublayers.count > 1) { //to avoid memory crash, need to remove all sublayer but keep the top one. [layer removeFromSuperlayer]; } idx += 1; } NSLog(@"sublayers after: %zd", self.view.layer.sublayers.count); }); //************* //the code bellow is the same of the above, but with no delay //uncomment the code bellow AND comment the code above to test // NSArray* sublayers = [NSArray arrayWithArray:self.view.layer.sublayers]; // NSInteger idx = 0; // // for (CALayer *layer in sublayers) { // // if (idx < self.view.layer.sublayers.count && self.view.layer.sublayers.count > 1) { // // //to avoid memory crash, need to remove all sublayer but keep the top one. // [layer removeFromSuperlayer]; // } // idx += 1; // } //************* //App's memory usage is about 14MB constantly, didn't increase on videos change. //TODO: need to test with more than 100 heavy videos. } -(void)configureGestures:(UIView *)view{ UISwipeGestureRecognizer *right = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(userDidSwipeScreen:)]; right.direction = UISwipeGestureRecognizerDirectionRight; right.delegate = self; [view addGestureRecognizer:right]; UISwipeGestureRecognizer *left = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(userDidSwipeScreen:)]; left.direction = UISwipeGestureRecognizerDirectionLeft; left.delegate = self; [view addGestureRecognizer:left]; } - (void)userDidSwipeScreen:(UISwipeGestureRecognizer *)swipeGestureRecognizer{ switch (swipeGestureRecognizer.direction) { case UISwipeGestureRecognizerDirectionLeft: [self nextZoomPanel];break; case UISwipeGestureRecognizerDirectionRight:[self prevZoomPanel];break; default: break; } } @end 

我找到了一个非常简单的解决方案(对于某些人来说可能太简单了,但对我来说它有用):在Interface Builder中,我将视图的背景颜色 (将video图层附加到其中)设置为黑色 。 所以它现在只是’闪烁’黑色……