ZFPlayer 在tableView列表中播放视频架构设计

需求背景

需要在如图所示的列表中播放视频,并且播放视频在对应的卡片上,滚动结束的时候, 完整露出封面图的第一个视频自动播放

请添加图片描述

分析

根据需求,是滚动的时候获取符合条件的cell,并且
在cell的封面图上播放视频,从上往下,第一个完全展示的
cell播放视频

这就要求,我们的播放器应该是页面级别的,而不应该是cell 级别的,并且ZFPlayer框架支持这种自动播放的需求,
但是和我们的有些不同,我们这个列表是多种cell同时存在的,但是只有一种cell支持自动播放,所以要求我们进行
定制化开发

获取当前适合条件的cell

其实zfplayer是有获取当前符合条件的cell的方法的,但是
和我们的判断条件不一样,并且滚动回调是在这里设置的
如图,这里要我们ZFPlayer必须存在,但是我们页面消失之后,回
销毁ZFplayer,如果再回到这个页面,再滚动页面的时候,就没有这个
回调了,所以,我们直接在页面的scrollview代理方法中调用
请添加图片描述

如下代码

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [super scrollViewDidEndDecelerating:scrollView];
    [self findCorrectCell];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    [super scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
    if (!decelerate) {
        [self findCorrectCell];
    }
}

- (void)findCorrectCell
{
    if (self.channelBannerTool.playerView.currentPlayerManager.isPlaying) {
        //如果正在播放广告, 不自动播放
        return;
    }
    NSArray *cells = self.tableView.visibleCells;
    CGFloat scrollViewMidY = CGRectGetHeight(self.tableView.frame)/2;
    /// The final playing indexPath.
    __block NSIndexPath *finalIndexPath = nil;
    @weakify(self)
    [cells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger idx, BOOL * _Nonnull stop) {
        @strongify(self)
        if (![cell conformsToProtocol:@protocol(ZFPlayerCellAutoPlayProtocol)] ||
            ![cell respondsToSelector:@selector(zf_cellShouldAutoplay)] ||
            ![(id<ZFPlayerCellAutoPlayProtocol>)cell zf_cellShouldAutoplay]) {
            //如果不支持自动播放,则返回
            return ;
        }
        UIView *playerView = [cell viewWithTag:AxCoverImageViewTag];
        if (!playerView) return;
        CGRect rect1 = [playerView convertRect:playerView.frame toView:self.tableView];
        CGRect rect = [self.tableView convertRect:rect1 toView:self.tableView.superview];
        /// playerView top to scrollView top space.
        CGFloat topSpacing = CGRectGetMinY(rect) - CGRectGetMinY(self.tableView.frame) - CGRectGetMinY(playerView.frame);
        /// playerView bottom to scrollView bottom space.
        CGFloat bottomSpacing = CGRectGetMaxY(self.tableView.frame) - CGRectGetMaxY(rect) + CGRectGetMinY(playerView.frame);
        CGFloat centerSpacing = ABS(scrollViewMidY - CGRectGetMidY(rect));
        NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
        /// Play when the video playback section is visible.
        if ((topSpacing - self.autoplaytopCover >= 0) && (bottomSpacing - bottomBarHeight >= 0)) {
            
            /// If you have a cell that is playing, stop the traversal.
            if (!finalIndexPath) {
                /*
                 !self.playerView.playingIndexPath 说明当前没有正在播放的视频,
                 这个时候是要播放视频的,
                 self.playerView.playingIndexPath.row > indexPath.row 说明当前播放的
                 虽然现在有视频正在播放,但并不是第一个适合播放的视频,
                 所以这时候需要取第一条适合播放的视频播放
                 */
                if ((!self.playerView.playingIndexPath || self.playerView.playingIndexPath.row > indexPath.row)
                    && [cell isKindOfClass:[PaperChannelVideoBaseCell class]]) {
                    listContObjectVO *listBO = self.dataList[indexPath.row];
                    if (![listBO isKindOfClass:[listContObjectVO class]]) {
                        return;
                    }
                    NSString *url =!isBlankString(listBO.videos[@"hdurl"])?listBO.videos[@"hdurl"]:listBO.videos[@"url"];
                    ;
                    BOOL isLiving = [self isLivingTypeBOWithChannelListBO:listBO];
                    if (isLiving) {
                        NSDictionary *videoDic = listBO.liveInfo.videoLivingRoomSrcs[0];
                        url = videoDic[@"videoUrl"];
                    }
                    if (isBlankString(url)) {
                        return;
                    }
                                   
                    finalIndexPath = indexPath;
                    PaperChannelVideoBaseCell *cell = (PaperChannelVideoBaseCell *) [self.tableView cellForRowAtIndexPath:indexPath];
                    [self playWithListBO:listBO cell:cell isClick:NO];
                }
            }
        }
    }];
}

cell自动停止播放逻辑

有自动开始播放,就有自动停止播放,我们要求是一个封面图顶部刚被盖住的时候,就停止播放,即如果封面无法完全展示,就停止播放

ZFPlayer是自带停止播放的功能的,
请添加图片描述
请添加图片描述
根据以上代码,我们可以看出,需要我们设置,遮盖的距离和消失的比例
我们设置如下

    //注意,距离是设置给tableView, 消失比例设置给ZFPlayerController
    self.tableView.videoScrollBottomDisapperPadding = bottomBarHeight;
    self.tableView.videoScrollTopDisapperPadding = SafeAreaTopHeight + 44*PLUS_SCALE;
    self.playerController.playerDisapperaPercent = 0.0000001;

创建PlayerController

    if (!self.playerView) {
        ZFAVPlayerManager *playmanger = [[ZFAVPlayerManager alloc]init];
        self.playerView = [[ZFPlayerController alloc]initWithScrollView:self.tableView playerManager:playmanger containerViewTag:AxCoverImageViewTag];
        self.playerView.stopWhileNotVisible = self.showSmallFloatView;
        self.playerView.controlView = self.controlView;
        self.playerView.isInlistCell = YES;
        self.playerView.onlySupportFullScreenForPortraitVideo = YES;
        self.playerView.canDownDragToScaleToExitFullScreen = YES;
        self.playerView.smallFloatView.fatherView = self.view;
        self.playerView.smallFloatView.toppadding = self.tableView.videoScrollTopDisapperPadding;
        self.playerView.playerApperaPercent = 0.3;
        self.playerView.playerDisapperaPercent = 1;
        self.playerView.analysisModel = videoBO.bigData_AnalysisModel;
//        config 竖视频的东西
        self.controlView.disableHorizontalPanWhenSpecialPortraitVideo = YES;
        ///湃客视频不显示画中画
        if ([videoBO.forwordType intValue]==MediaNumberVideoNewsForwardType||
            [videoBO.forwordType intValue]==PaiGuestVideoNewsForwardType) {
            self.controlView.portraitControlView.isCanShowButton = NO;
        }else{
            self.controlView.portraitControlView.isCanShowButton = YES;
            ///画中画-与广告角标冲突
            self.controlView.controlViewAppearedCallback = ^(BOOL appeared) {
                if (appeared) {
                    cell.cornerDesc.alpha = 0;
                }else{
                    cell.cornerDesc.alpha = 1;
                }
            };
        }
        
        
        WEAKSELF
        self.landScapeControlView.shareButtonAction = ^{
            [weakSelf shareHanderWith:videoBO withBtn:[UIButton new]];
        };
        
        
        self.landScapeControlView.pushToDetailVideoContent = ^(listContObjectVO * _Nonnull channelBO) {
            [weakSelf channelPsuhWithListBO:channelBO indexpath:channelBO.indexPath];
        };
    
        
//        防止瀑布流滑动的时候会触发声音或者亮度的滑动时间
        self.playerView.disablePanMovingDirection = ZFPlayerDisablePanMovingDirectionVertical;
//        WEAKSELF
        self.playerView.cancleActionWhen4Gplay = ^{
            [weakSelf resetPlayerView];
        };
        
        self.playerView.smallFloatView.closeClickCallback = ^{
            [weakSelf resetPlayerView];
        };
        
        self.playerView.playerDidToEnd = ^(id<ZFPlayerMediaPlayback>  _Nonnull asset) {
            [weakSelf resetPlayerView];
        };
        
        self.controlView.playerDidToStopPlayVideo = ^{
            [weakSelf resetPlayerView];
        };
        self.playerView.currentPlayerManager.muted = ![TPUserDefault   instance].openChannelAutoPlayVoice;
        self.playerView.playerApperaPercent = 1;
        self.playerView.playerDisapperaPercent = 0.0000001;
        self.playerView.onlySupportFullScreenForPortraitVideo = NO;
        if (self.playerView.gestureControl.panGR &&        [self.playerView.currentPlayerManager.view.gestureRecognizers   containsObject:self.playerView.gestureControl.panGR]) {
            [self.playerView.currentPlayerManager.view removeGestureRecognizer:self.playerView.gestureControl.panGR];
        }
        WEAKSELF
        self.playerView.playerPlayTimeChanged = ^(id<ZFPlayerMediaPlayback>  _Nonnull asset, NSTimeInterval currentTime, NSTimeInterval duration) {
        weakSelf.currentTime = currentTime;
        if (currentTime >= duration) {
            weakSelf.currentTime = 0;
        }
    };