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;
        }
    };

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/207682.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSS中的非布局样式+CSS布局 前端开发入门笔记(十一)

CSS中的非布局样式 在CSS中&#xff0c;非布局样式是指那些不会直接影响页面布局的样式。这些样式主要关注的是元素的颜色、字体、背景、边框、阴影等视觉效果。以下是一些常见的非布局CSS样式&#xff1a; 文本样式&#xff1a;包括字体&#xff08;font-family&#xff09;…

传统算法:使用 Pygame 实现归并排序

使用 Pygame 模块实现了归并排序的动画演示。首先,它生成一个包含随机整数的数组,并通过 Pygame 在屏幕上绘制这个数组的条形图。接着,通过归并排序算法对数组进行排序,动画效果可视化每一步的排序过程。在排序的过程中,程序将数组递归地分成两半,分别进行排序,然后再将…

小白备战蓝桥杯:Java常用API

一、什么是API 就是别人写好的一些类&#xff0c;给咱们程序员直接拿去调用即可解决问题的 我们之前接触过的Scanner和Random都是API 但java中提供的API很多&#xff0c;我们没有必要去学习所有的API&#xff0c;只需要知道一些常用的API&#xff0c;再借助帮助文档去使用AP…

从HumanEval到CoderEval: 你的代码生成模型真的work吗?

本文主要介绍了一个名为CoderEval的代码生成大模型评估基准&#xff0c;并对三个代码生成模型&#xff08;CodeGen、PanGu-Coder和ChatGPT&#xff09;在该基准上的表现进行了评估和比较。研究人员从真实的开源项目中的选取了代码生成任务来构建CoderEval&#xff0c;并根据对外…

Python函数专题(下)侯小啾python领航班系列(十三)】

Python函数专题(下)侯小啾python领航班系列(十三)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

腾讯云年末感恩回馈:2核2G4M云服务器118元1年,新老用户同享!

腾讯云年末感恩回馈活动开始了&#xff0c;年度爆款2核2G4M云服务器118元/年&#xff0c;新老用户同享&#xff0c;记得抓住上云好时机&#xff01; 活动地址&#xff1a; 点此直达腾讯云年末感恩回馈 活动详情&#xff1a; 配置说明&#xff1a; 2核2G 独享CPU性能50GB SSD…

观《王牌对王牌:国宝回国》有感 —— AI绘画之古画修复对比图

一、前言 上周《王牌对王牌》节目的主题是《国宝回国》&#xff0c;而今天的AI绘画的灵感&#xff0c;就来源于这期节目。 下面这组图&#xff0c;左侧部分因时间的流逝而显现出褪色和损伤的痕迹&#xff0c;色彩变得暗淡&#xff0c;细节也因年代久远而变得模糊不清。 而右…

知虾平台丨优化Shopee店铺运营,提升销售利润——了解知虾平台

在如今竞争激烈的电商市场中&#xff0c;Shopee作为一家快速发展的平台&#xff0c;吸引了众多卖家加入。然而&#xff0c;要在Shopee上取得成功并实现可观的销售利润&#xff0c;并不是一件容易的事情。为了帮助卖家更好地了解市场趋势、优化商品关键词、监控竞争对手等&#…

c题目13:验证100以内的数是否满足哥德巴赫猜想。(任一大于2的偶数都可以写成两个质数之和)

每日小语 活下去的诀窍是&#xff1a;保持愚蠢&#xff0c;又不能知道自己有多蠢。——王小波 自己思考 即要让第一个质数与这个数减去第一个质数的值都为质数&#xff0c;所以要满足几个条件 1.abc 2.a为质数 3.b为质数 这里是否可以用到我之前刚学的自己设置的那个判断…

daima8资源网整站数据打包完整代码(集成了ripro9.1主题,开箱即用)

基于ripro9.1完全明文无加密后门版本定制开发&#xff0c;无需独立服务器&#xff0c;虚拟主机也可以完美运营&#xff0c;只要主机支持php和mysql即可。整合了微信登录和几款第三方的主题文件&#xff0c;看起来更美观一些。站长本人就是程序员&#xff0c;所以本站的代码资源…

力扣每日一题(2023-11-30)

力扣每日一题 题目&#xff1a;1657. 确定两个字符串是否接近 日期&#xff1a;2023-11-30 用时&#xff1a;21 m 07 s 时间&#xff1a;11ms 内存&#xff1a;43.70MB 代码&#xff1a; class Solution {public boolean closeStrings(String word1, String word2) {if(word1.…

全面预算管理平台让企业管理智慧升级

智能制造背景下&#xff0c;企业财务发展与业务、运营、服务等环节紧紧相扣&#xff0c;并逐渐体现出智慧化的特性。区别于传统的商业智能BI&#xff0c;智慧管理平台作为企业数字化转型的核心&#xff0c;通过信息系统的集成&#xff0c;能够对企业各个业务模块进行整合&#…

Service的双向跨进程通信

一、客户端向服务端通信。 1、创建AIDL文件&#xff0c;用于生成跨进程通信代码。 // ITestService.aidl package com.example.servicetest;interface ITestService {void sayHello(); } 2、创建服务端Service&#xff0c;添加如下代码。 public class TestService extends…

spring cloud gateway源码分析,一个请求进来的默认处理流程

1.前言 spring cloud gateway的基本组成和作用就不细赘述&#xff0c;此篇适合对此有一定了解的人阅读。 spring cloud gateway版本: Hoxton.SR1 spring cloud gateway的配置使用yml配置&#xff1a; server:port: 9527y#根据微服务名称进行动态路由的配置 spring:applicati…

elk+filebeat+kafka集群部署

EFK实验架构图&#xff1a; 实现高并发&#xff0c;无需指定logstash 3台esfile&#xff0c;3台kafka 20.0.0.10 esfile 20.0.0.20 esfile 20.0.0.30 esfile 20.0.0.11 kafka 20.0.0.12 kafka 20.0.0.13 kafka在es1主机上解压filebeat cd filebeat 安装nginx服务 vim /usr/loc…

Prometheus集群模式部署日记-主节点篇

Prometheus架构组件主节点masterPrometheus-master&#xff0c;Alertmanger&#xff0c;Grafana&#xff0c;PrometheusAlert子节点node(不同网络&#xff0c;混合云环境)Prometheus-node1 所有组件均采用docker-compose部署&#xff0c;docker 和 compose 请自行安装不再赘述…

软件开发的生命周期:从构想到维护

目录 需求分析阶段 设计阶段 实现阶段 测试阶段 部署阶段 维护阶段 结语 软件开发是一项复杂而又精密的工程&#xff0c;它的整个过程被称为软件开发生命周期。这一生命周期涵盖了从最初构想到最终维护的各个阶段&#xff0c;每个阶段都有其独特的任务和活动。在本文中&…

计算一个4+4+1的队形变换问题

2 2 1 1 2 2 2 2 1 1 2 2 3 3 A A 3 3 4 4 A 12 4 4 4 4 12 A 4 4 2 2 1 1 2 2 操场上有4个人以4a1的结构在6*6的平面上运动&#xff0c;行分布是0&#xff0c;0&#xff0c;0&#xff0c;1&#xff0c;1&#xff0c;2&#xff0c;列分布…

【爬虫逆向分析实战】某笔登录算法分析——本地替换分析法

前言 作者最近在做一个收集粉币的项目&#xff0c;可以用来干嘛这里就不展开了&#x1f601;&#xff0c;需要进行登录换算token从而达到监控收集的作用&#xff0c;手机抓包发现他是通过APP进行计算之后再请求接口的&#xff0c;通过官网分析可能要比APP逆向方便多&#xff0…

SSM项目实战-service实现

1、SysUserService.java package com.atguigu.schedule.service;import com.atguigu.schedule.pojo.SysUser;public interface SysUserService {SysUser getSysUser(SysUser sysUser); }2、SysUserServiceImpl.java package com.atguigu.schedule.service.impl; import com.atg…