ios - UITableViewCell 打嗝中的自动播放视频

标签 ios uitableview autoplay feed

我已经阅读了 StackOverflow 上有关自动播放视频的大部分问题,并且我能够在 UITableView 中自动播放它们,但我遇到的问题很少,如下所述

  1. 视频开始时滚动会暂停一秒钟
  2. 播放前视频闪烁
  3. 如果我向上滚动,视频不会自动播放

我想要的是在不使用任何第三方库(如 ASYNCDisplayKit)的情况下像 Facebook 一样自动播放视频的流畅体验。 所有视频 url 均来自 AWSS3 云端 URLS。 我还上传了该问题的视频,以防有人想看。

Video Autoplay Hiccups

这是我的完整代码

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {



            PostViewModel* model = self.posts[indexPath.section];
            Post* post = model.post;

            PostItems* item = model.items[indexPath.row];
                if(item.itemType == nameAndPicture) {
                    //Removed code as it's not related to question
                }
                else if(item.itemType == textContent){
                    //Removed code as it's not related to question
                }
                else if(item.itemType == images){
                    //Removed code as it's not related to question
                }
                else if(item.itemType == videos){

                    VideoListCell *cell = nil;
                    cell = (VideoListCell*)[tableView dequeueReusableCellWithIdentifier:kFeedVideoListCellIdentifier forIndexPath:indexPath];
                    cell.delegate = self;
                    cell.indexPath = indexPath;
                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
                    cell.backgroundColor = [UIColor clearColor];

                    cell.videoThumbnail.image = nil;

                    [cell setCounter:post.medias.count];

                    if (post.medias.count > 0) {
                        MediaItem* item = post.medias[0];
                        if ([item getMediaType] == VIDEO) {

                           NSString* thumbnailURL = item.thumbnailUrl;
                            [cell.videoThumbnail downloadImageWithURL:thumbnailURL andPlaceholderImage:self.timelinePlaceholder indicatorStyle:UIActivityIndicatorViewStyleWhiteLarge cachePolicy:NSURLRequestReturnCacheDataElseLoad andTimeOut:120];
                            [cell hideVideoAndShowThumbnail];

                            dispatch_async(dispatch_get_main_queue(), ^{
                                [cell setMediaItem:item withUserID:post.userId];
                            });
                        }
                    }
                    cell.clipsToBounds = YES;
                    return cell;
                }

}




 - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{

    //Check if the cell displayed is video cell then try to autoplay the video
    if([cell isKindOfClass:[VideoListCell class]]){
        VideoListCell* videoCell = (VideoListCell*)cell;
        dispatch_async(dispatch_get_main_queue(), ^{
            [videoCell hideVideoAndShowThumbnail];
        });
        PostViewModel* model = self.posts[indexPath.section];
        Post* post = model.post;
        PostItems* item = model.items[indexPath.row];
        if(item.itemType == videos){
            videoCell.videoThumbnail.image = nil;
            [videoCell setCounter:post.medias.count];

            if (post.medias.count > 0) {
                MediaItem* item = post.medias[0];
                if ([item getMediaType] == VIDEO) {
                    //dispatch_async(dispatch_get_main_queue(), ^{
                    NSString* profilePic = item.thumbnailUrl;
                    [videoCell.videoThumbnail downloadImageWithURL:profilePic andPlaceholderImage:self.timelinePlaceholder indicatorStyle:UIActivityIndicatorViewStyleWhiteLarge cachePolicy:NSURLRequestReturnCacheDataElseLoad andTimeOut:120];
                    [videoCell setMediaItem:item withUserID:post.userId];
                    [videoCell playVideo];
                }
            }
        }
    }
}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath {

    if([cell isKindOfClass:[VideoListCell class]]){

        VideoListCell* videoCell = (VideoListCell*)cell;
        [videoCell stopVideo];
        videoCell.avLayer = nil;
        videoCell.videoPlayer = nil;
        [videoCell hideVideoAndShowThumbnail];
    }
}

//视频列表单元类

#define kHeight 200

@implementation VideoListCell

- (void)awakeFromNib {
    [super awakeFromNib];

    UIImage* icon = [[UIImage imageNamed:@"play-icon"] imageTintedWithColor:kSliderDarkYellowColor];
    [self.btnPlay setImage:icon forState:UIControlStateNormal];

    UIImage* pauseIcon = [[UIImage imageNamed:@"pause-icon"] imageTintedWithColor:kSliderDarkYellowColor];

    [self.btnPlay setImage:icon forState:UIControlStateNormal];
    [self.btnPlay setImage:pauseIcon forState:UIControlStateSelected];


    UITapGestureRecognizer *viewTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapOnView)];
    viewTap.numberOfTapsRequired = 1;
    self.viewPlayer.userInteractionEnabled = YES;
    [self.viewPlayer addGestureRecognizer:viewTap];

    self.counterView.hidden = YES;
    self.counterView.layer.cornerRadius = 12.0f;
    self.counterView.layer.masksToBounds = YES;

    //Add Gesture to label
    UITapGestureRecognizer *countGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapOnCounterView)];
    countGesture.numberOfTapsRequired = 1;
    self.counterView.userInteractionEnabled = YES;
    [self.counterView addGestureRecognizer:countGesture];

    [self.btnFullScreen addTarget:self action:@selector(btnFSTapped:) forControlEvents:UIControlEventTouchUpInside];

    self.btnFullScreen.hidden = NO;
    UIImage* fullScreenImage = [[UIImage imageNamed:@"fullScreenIcon"] imageTintedWithColor:kSliderDarkYellowColor];
    [self.btnFullScreen setImage:fullScreenImage forState:UIControlStateNormal];

}

- (void)showThumbnail:(BOOL)yesOrNo {
    self.videoThumbnail.hidden = !yesOrNo;
    self.viewForVideo.hidden = yesOrNo;
}

- (void)hideVideoAndShowThumbnail {
    [self stopVideo];
    [self showThumbnail:YES];
    self.btnPlay.selected = NO;
    self.isPlaying = NO;
}

- (void)btnFSTapped:(UIButton*)sender {
    if (self.delegate && [self.delegate respondsToSelector:@selector(fullScreenButtonTapped:andURL:andPlayer:)]) {
        [self.delegate fullScreenButtonTapped:self.indexPath andURL:self.videoURL andPlayer:self.player.player];
    }
}

- (void)layoutSubviews
{
    [super layoutSubviews];
//     if (self.avLayer) {
//         [self.avLayer setFrame:CGRectMake(self.viewForVideo.frame.origin.x, self.viewForVideo.frame.origin.y, self.viewForVideo.frame.size.width,  self.viewForVideo.frame.size.height)];
//     }

}

- (void)initNewPlayerItem {
    // Pause the existing video (if there is one)
    //[self stopVideo];

    if(self.asset){
        [self.asset cancelLoading];
    }


    // First we need to make sure we have a valid URL
    if (!self.videoURL) {
        return;
    }

    // Create a new AVAsset from the URL
    self.asset = [AVAsset assetWithURL:self.videoURL];

    // Now we need an AVPlayerItem to pass to the AVPlayer
    AVPlayerItem* item  = [[AVPlayerItem alloc] initWithAsset:self.asset];

    if(item){
        [[NSNotificationCenter defaultCenter] addObserver:self

                                                 selector:@selector(playerItemDidReachEnd:)

                                                     name:AVPlayerItemDidPlayToEndTimeNotification

                                                   object:item];
    }
    //[self.player.player replaceCurrentItemWithPlayerItem:item];

    // Finally, we set this as the current AVPlayer item

    [self.asset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{

        NSError* error = nil;
        AVKeyValueStatus status = [self.asset statusOfValueForKey:@"duration" error:&error];
        if (status == AVKeyValueStatusFailed) {
            [self.playerSetupLoading stopAnimating];
            self.btnPlay.hidden = NO;
            self.btnPlay.selected = NO;
            return;
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.playerSetupLoading stopAnimating];
            [self.player.player replaceCurrentItemWithPlayerItem:item];
            self.btnPlay.selected = YES;
            self.btnPlay.hidden = YES;
            [self showThumbnail:NO];
            [self.player.player play];
            self.isPlaying = YES;
        });
    }];
}

- (void)playerItemDidReachEnd:(NSNotification*)notif {

    id object = [notif object];
    if (object && [object isKindOfClass:[AVPlayerItem class]]) {
        AVPlayerItem* item = (AVPlayerItem*)[notif object];
        [item seekToTime:kCMTimeZero];
    }
    //[self stopVideo];
    [self showThumbnail:YES];
    self.btnPlay.selected = NO;
    self.btnPlay.hidden = NO;

}

-(void)prepareForReuse {

//    self.videoURL = nil;
//    self.videoThumbnail.image = nil;
    //[self.player pauseContent];
    self.videoThumbnail.image = nil;
    if (self.avLayer.superlayer) {
        [self.avLayer removeFromSuperlayer];
    }

    if (self.viewForVideo.subviews.count > 0) {
        for (UIView* v in self.viewForVideo.subviews) {
            [v removeFromSuperview];
        }
    }

    self.videoURL = nil;
    self.player = nil;
    self.userID = nil;
    self.videoItem = nil;
    self.videoPlayer = nil;
    self.btnPlay.selected = NO;

    [super prepareForReuse];
}

- (void)tapOnView {

    //if(self.counterView.hidden){
        if (self.delegate && [self.delegate respondsToSelector:@selector(fullScreenButtonTapped:andURL:andPlayer:)]) {
            [self.delegate fullScreenButtonTapped:self.indexPath andURL:self.videoURL andPlayer:self.player.player];
        }
    //}
//    else
//    {
//            if (self.delegate && [self.delegate respondsToSelector:@selector(playVideo:withURL:)]) {
//                [self.delegate playVideo:self.indexPath withURL:nil];
//            }
//    }
}

-(void)tapOnCounterView {
    if (self.delegate && [self.delegate respondsToSelector:@selector(playVideo:withURL:)]) {
          [self.delegate playVideo:self.indexPath withURL:nil];
    }
}

- (void)setCounter:(NSUInteger)count {

    if (count > 1) {
        self.counterView.hidden = NO;
        self.lblCounter.text = [NSString stringWithFormat:@"+%lu more",(unsigned long)count-1];
    }
    else{
        self.counterView.hidden = YES;
    }
}

- (IBAction)btnPlayTapped:(id)sender {

    //[self playVideo];

     //if(self.counterView.hidden){
        if(self.btnPlay.selected){
            [self stopVideo];
            self.btnPlay.selected = NO;
        }else{
            [self playVideo];
            self.btnPlay.selected = YES;
        }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if(self.player.player.timeControlStatus == AVPlayerTimeControlStatusPlaying){
        if(self.btnPlay.hidden){
            self.btnPlay.hidden = NO;
        }
    }
}


- (void)playVideo {

    //if (!self.player) {
        if ([self.videoObject doesPreSignedURLExpired]) {

            //Call API here and update media item object URL
            dispatch_async(dispatch_get_main_queue(), ^{
                //Call API here
                //URL is expired then give a call to our server to generate a new URL
                [self generateNewPreSignedURL];
            });
        }
        else{
            if (!self.videoURL) {

                dispatch_async(dispatch_get_main_queue(), ^{
                    [self generatePreSignedURLWithVideoThumbnail];
                });
            }else{
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self setupPlayer];

                });
            }
        }
}

- (void)stopVideo {
    if (self.player) {
        self.isPlaying = NO;
        self.btnPlay.hidden = NO;
        [self.player.player pause];
    }
}

- (void)setMediaItem:(MediaItem*)item withUserID:(NSNumber*)userId {
    self.videoObject = item;
    self.userID = userId;
    [self showThumbnail:YES];
}

- (void)generatePreSignedURLWithVideoThumbnail {

    if (self.videoObject.mediaUrl && [self.videoObject hasPreSignedURL]) {
        //Already have pre signed url check if URL is expired
        //If URL expired then call our own server to generate a new presigned URL
        dispatch_async(dispatch_get_main_queue(), ^{
            self.videoURL = [NSURL URLWithString:self.videoObject.mediaUrl];
            [self setupPlayer];
        });
    }
    else if(self.videoObject.mediaUrl && [self.videoObject hasPlayListURL]){
        AppDelegate* delegate = [AppDelegate applicationDelegate];
        AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new];
        getPreSignedURLRequest.bucket = S3BucketName;
        getPreSignedURLRequest.key = kS3OutputVideoFileInternalPath(delegate.loggedInUser.userId,[self.videoObject getVideoFolderName],self.videoObject.mediaUrl);

        getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodGET;
        getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:Hour*24*5];

        [[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest]
         continueWithBlock:^id(AWSTask *task) {
             if (task.error) {
                 NSLog(@"Error: %@",task.error);
             } else {
                 dispatch_async(dispatch_get_main_queue(), ^{
                     self.videoURL = task.result;
                     [self setupPlayer];
                 });
             }
             return nil;
         }];
    }
    else{
        //Generate Pre signed URL
        AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new];
        getPreSignedURLRequest.bucket = S3BucketName;
        getPreSignedURLRequest.key = [kS3InputVideoFilePath(self.userID) stringByAppendingString:self.videoObject.mediaUrl];

        getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodGET;
        getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:Hour*24*5];

        [[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest]
         continueWithBlock:^id(AWSTask *task) {
             if (task.error) {
                 NSLog(@"Error: %@",task.error);
             } else {
                 dispatch_async(dispatch_get_main_queue(), ^{
                     self.videoURL = task.result;
                     [self setupPlayer];

                 });
             }
             return nil;
         }];
    }
}

- (void)setupPlayer {
    self.btnPlay.hidden = YES;

    self.videoItem = nil;
    self.videoPlayer = nil;

    self.videoItem = [[AVPlayerItem alloc] initWithURL:self.videoURL];

    if (self.avLayer.superlayer) {
        [self.avLayer removeFromSuperlayer];
    }

    if (self.viewForVideo.subviews.count > 0) {
        for (UIView* v in self.viewForVideo.subviews) {
            [v removeFromSuperview];
        }
    }

    self.videoPlayer = [[AVPlayer alloc] initWithPlayerItem:self.videoItem];

    self.avLayer = [AVPlayerLayer playerLayerWithPlayer:self.videoPlayer];
    self.avLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    self.player = [[AVPlayerViewController alloc] init];
    self.player.player = self.videoPlayer;
    self.player.videoGravity = AVLayerVideoGravityResizeAspectFill;

    self.player.showsPlaybackControls = NO;

    // Insert the player into the cell view hierarchy and setup autolayout
    self.player.view.translatesAutoresizingMaskIntoConstraints = false;
    [self.viewForVideo insertSubview:self.player.view atIndex:0];

    //Trailing
    NSLayoutConstraint *trailing =[NSLayoutConstraint
                                   constraintWithItem:self.player.view
                                   attribute:NSLayoutAttributeTrailing
                                   relatedBy:NSLayoutRelationEqual
                                   toItem:self.viewForVideo
                                   attribute:NSLayoutAttributeTrailing
                                   multiplier:1.0f
                                   constant:0.f];

    //Leading

    NSLayoutConstraint *leading = [NSLayoutConstraint
                                   constraintWithItem:self.player.view
                                   attribute:NSLayoutAttributeLeading
                                   relatedBy:NSLayoutRelationEqual
                                   toItem:self.viewForVideo
                                   attribute:NSLayoutAttributeLeading
                                   multiplier:1.0f
                                   constant:0.f];

    //Bottom
    NSLayoutConstraint *bottom =[NSLayoutConstraint
                                 constraintWithItem:self.player.view
                                 attribute:NSLayoutAttributeBottom
                                 relatedBy:NSLayoutRelationEqual
                                 toItem:self.viewForVideo
                                 attribute:NSLayoutAttributeBottom
                                 multiplier:1.0f
                                 constant:0.f];

    //Height to be fixed for SubView same as AdHeight
    NSLayoutConstraint *height = [NSLayoutConstraint
                                  constraintWithItem:self.player.view
                                  attribute:NSLayoutAttributeHeight
                                  relatedBy:NSLayoutRelationEqual
                                  toItem:nil
                                  attribute:NSLayoutAttributeNotAnAttribute
                                  multiplier:0
                                  constant:kHeight];

    //Add constraints to the Parent
    [self.viewForVideo addConstraint:trailing];
    [self.viewForVideo addConstraint:bottom];
    [self.viewForVideo addConstraint:leading];

    //Add height constraint to the subview, as subview owns it.
    [self.player.view addConstraint:height];

    [self initNewPlayerItem];
}

- (void)generateNewPreSignedURL {

    if (self.videoObject) {
        NSDictionary* postParams = @{kMediaId:self.videoObject.mediaId};


        dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
            TBWebAPIConsumer *web = [TBWebAPIConsumer sharedWebAPIManager];
            [web generatePreSignedURL:postParams andCompletionBlock:^(NSError *error, id serverResponse) {

                // Do something...
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (error == nil){
                        //Parse user data here
                        NSDictionary* data = (NSDictionary*)serverResponse;

                        if (![data valueForKeyIsNull:@"mediaUrl"]) {
                            self.videoObject.mediaUrl = [data valueForKey:@"mediaUrl"];
                        }
                        if (![data valueForKeyIsNull:@"videoSignedUrlExpiry"]) {
                            self.videoObject.videoSignedUrlExpiry = [data valueForKey:@"videoSignedUrlExpiry"];
                        }
                        [self generatePreSignedURLWithVideoThumbnail];

                    }
                });

            }];

        });
    }


}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

有人可以帮我解决这个问题吗?

最佳答案

我有一些建议给你:

  1. 不要尝试在滚动期间调用播放/暂停。滚动停止后立即执行此操作:https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

  2. 您可以尝试使用这个肮脏的技巧:https://gist.github.com/k06a/66f7815b0325f239411e26f498c75755要对 Apple Review Team 隐藏它,只需使用 UAObfuscateString 库混淆键路径 @"_player.stateDispatchQueue"

  3. 我听说无需肮脏的黑客攻击即可获得平稳的投资返回。我会请我的一个 friend 来回答你的问题。

关于ios - UITableViewCell 打嗝中的自动播放视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51787983/

相关文章:

ios - 将图像写入 ALAssetsLibrary 会导致不同的文件哈希

ios - 使用 Apple Enterprise Developer Program 部署 iOS 应用程序

jquery - Fotorama slider : How can I get the slider to pause on mouse over the resume auto play when mouse out?

video - 禁用YouTube的 “up next”/自动播放功能的脚本或插件

带有 Fancybox 的 HTML5 视频

html - 无法在 iOS webview 中删除电话号码的文字修饰

ios - 我想在我的 iOS 游戏中构建一个大型社交异步游戏模式,是否有适用于此的 iOS API?

ios - 搜索结果在 DetailViewController (Swift 3) 中未显示正确的数据

ios - 从另一个 View 重新加载 TableView

ios - 如何通过单元格内的按钮将 NSIndexPath 传递到另一个 ViewController?