首页 文章

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

提问于
浏览
14

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

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

这一切都有效,但是,有一个 prominent 屏幕"flicker" .

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

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

有没有人知道是否有办法停止这个flickr,要么通过AVPlayer *类,要么通过做某事来做到这一点,所以这是不可见的 . -2918055_

谢谢

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

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

5 回答

  • 0

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

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

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

  • 0

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

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

    #import "ViewController.h"
    #import <AVKit/AVKit.h>
    
    @interface ViewController ()<UIGestureRecognizerDelegate>
    @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
    
  • 3

    我找到了一个非常简单的解决方案(对于某些人来说可能太简单了,但对我来说它确实有效):在Interface Builder中我 set the background color of my view (它附加了视频层) to black . 所以's just '闪现'黑色现在......

  • -1

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

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

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

  • 6

    您无需为此任务重新创建 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];
             }
         }
     }
    

相关问题