美文网首页iOS 音视频iOS DeveloperiOS音视频
使用AVPlayer自定义支持全屏的播放器(四)

使用AVPlayer自定义支持全屏的播放器(四)

作者: 季末微夏 | 来源:发表于2017-08-15 14:12 被阅读1943次

    前言

    前段时间封装了一个视频播放器使用AVPlayer自定义支持全屏的播放器(三),经过一段时间的测试,发现了许多bug,针对以前遗留的问题进行了修复和更新。

    修复bug

    主要修复了播放器页面不支持旋转引起全屏音量图标未旋转,进度条拖拽不灵敏,Masonry引起约束警告,网络不好销毁播放器引起卡顿,工具条自动消失后需要点击两次等bug。

    1.旋转后音量图标不旋转bug

    开始使用的是旋转播放器来实现全屏,实际页面未旋转,所以系统音量图标方向不对,修改后利用页面旋转来实现全屏,这样就不会引起系统音量图标方向不对,具体如何使页面支持旋转,并且不影响其他页面请看这里iOS页面旋转详解

    2.进度条拖拽不灵敏

    由于自定义了进度条的图标,引起进度条拖拽不灵敏,这里在自定义进度条内部重写- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event这两个方法,增大响应的范围。

    代码
    //检查点击事件点击范围是否能够交给self处理
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        //调用父类方法,找到能够处理event的view
        UIView* result = [super hitTest:point withEvent:event];
        if (result != self) {
            /*如果这个view不是self,我们给slider扩充一下响应范围,
             这里的扩充范围数据就可以自己设置了
             */
            if ((point.y >= -15) &&
                (point.y < (_lastBounds.size.height + SLIDER_Y_BOUND)) &&
                (point.x >= 0 && point.x < CGRectGetWidth(self.bounds))) {
                //如果在扩充的范围类,就将event的处理权交给self
                result = self;
            }
        }
        //否则,返回能够处理的view
        return result;
    }
    //检查是点击事件的点是否在slider范围内
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
        //调用父类判断
        BOOL result = [super pointInside:point withEvent:event];
        if (!result) {
            //同理,如果不在slider范围类,扩充响应范围
            if ((point.x >= (_lastBounds.origin.x - SLIDER_X_BOUND)) && (point.x <= (_lastBounds.origin.x + _lastBounds.size.width + SLIDER_X_BOUND))
                && (point.y >= -SLIDER_Y_BOUND) && (point.y < (_lastBounds.size.height + SLIDER_Y_BOUND))) {
                //在扩充范围内,返回yes
                result = YES;
            }
        }
        //否则返回父类的结果
        return result;
    }
    

    新增功能

    新增加了转子动画,增加拖拽后转子衔接动画,增加各类接口。

    转子动画

    利用CAShapeLayerUIBezierPath做了一个简单的加载动画。

    代码
    @interface AILoadingView ()<CAAnimationDelegate>
    
    @property(nonatomic,strong)CAShapeLayer *loadingLayer;
    /** 当前的index*/
    @property(nonatomic,assign)NSInteger index;
    /** 是否能用*/
    @property(nonatomic,assign,getter=isEnable)BOOL enable;
    @end
    @implementation AILoadingView
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            _index    = 0;
            _enable   = YES;
            _duration = 2.;
            [self createUI];
        }
        return self;
    }
    -(void)layoutSubviews {
        [super layoutSubviews];
        UIBezierPath *path      = [self cycleBezierPathIndex:_index];
        self.loadingLayer.path  = path.CGPath;
    }
    
    - (UIBezierPath*)cycleBezierPathIndex:(NSInteger)index {
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height *0.5) radius:self.bounds.size.width * 0.5 startAngle:index * (M_PI* 2)/3  endAngle:index * (M_PI* 2)/3 + 2*M_PI * 4/3 clockwise:YES];
        return path;
    }
    - (void)createUI {
        self.loadingLayer             = [CAShapeLayer layer];
        self.loadingLayer.lineWidth   = 2.;
        self.loadingLayer.fillColor   = [UIColor clearColor].CGColor;
        self.loadingLayer.strokeColor = [UIColor blackColor].CGColor;
        [self.layer addSublayer:self.loadingLayer];
        self.loadingLayer.lineCap     = kCALineCapRound;
    }
    - (void)loadingAnimation {
        CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
        strokeStartAnimation.fromValue         = @0;
        strokeStartAnimation.toValue           = @1.;
        strokeStartAnimation.timingFunction    = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        
        CABasicAnimation *strokeEndAnimation   = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        strokeEndAnimation.fromValue           = @.0;
        strokeEndAnimation.toValue             = @1.;
        strokeEndAnimation.duration            = self.duration * 0.5;
        
        CAAnimationGroup *strokeAniamtionGroup = [CAAnimationGroup animation];
        strokeAniamtionGroup.duration          = self.duration;
        
        strokeAniamtionGroup.delegate          = self;
        strokeAniamtionGroup.animations        = @[strokeEndAnimation,strokeStartAnimation];
        [self.loadingLayer addAnimation:strokeAniamtionGroup forKey:@"strokeAniamtionGroup"];
    }
    #pragma mark -CAAnimationDelegate
    -(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
        if (!self.isEnable) {
            return;
        }
        _index++;
        self.loadingLayer.path = [self cycleBezierPathIndex:_index %3].CGPath;
        [self loadingAnimation];
    }
    
    #pragma mark -public
    - (void)starAnimation {
        if (self.loadingLayer.animationKeys.count > 0) {
            return;
        }
        self.hidden = NO;
        self.enable = YES;
        [self loadingAnimation];
    }
    - (void)stopAnimation {
        self.hidden = YES;
        self.enable = NO;
        [self.loadingLayer removeAllAnimations];
    }
    - (void)setStrokeColor:(UIColor *)strokeColor {
        _strokeColor                   = strokeColor;
        self.loadingLayer.strokeColor  = strokeColor.CGColor;
    }
    

    使用方法

    使用cocoapods导入,pod 'CLPlayer'

    1.普通页面

    支持通过Push和模态创建的页面,无论页面是否支持旋转都兼容。播放器默认全部页面都只支持竖屏,如果使用后需要某个页面支持其他方向,需要重写下面几个方法。

    页面支持其他方向代码
    #pragma mark -- 需要页面支持其他方向,需要重写这三个方法,默认所有页面只支持竖屏
    // 是否支持自动转屏
    - (BOOL)shouldAutorotate {
        return YES;
    }
    // 支持哪些屏幕方向
    - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
        return UIInterfaceOrientationMaskAll;
    }
    // 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法)
    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
        return UIInterfaceOrientationPortrait;
    }
    
    使用代码
        CLPlayerView *playerView = [[CLPlayerView alloc] initWithFrame:CGRectMake(0, 90, self.view.CLwidth, 300)];    
        [self.view addSubview:playerView];    
    //    //重复播放,默认不播放
    //    playerView.repeatPlay = YES;
    //    //当前控制器是否支持旋转,当前页面支持旋转的时候需要设置,告知播放器
    //    playerView.isLandscape = YES;
    //    //设置等比例全屏拉伸,多余部分会被剪切
    //    playerView.fillMode = ResizeAspectFill;
    //    //设置进度条背景颜色
    //    playerView.progressBackgroundColor = [UIColor purpleColor];
    //    //设置进度条缓冲颜色
    //    playerView.progressBufferColor = [UIColor redColor];
    //    //设置进度条播放完成颜色
    //    playerView.progressPlayFinishColor = [UIColor greenColor];
    //    //全屏是否隐藏状态栏
    //    playerView.fullStatusBarHidden = NO;
    //    //是否静音,默认NO
    //    playerView.mute = YES;
    //    //转子颜色
    //    playerView.strokeColor = [UIColor redColor];
        //视频地址
        playerView.url = [NSURL URLWithString:@"http://baobab.wdjcdn.com/14587093851044544c.mp4"];
        //播放
        [playerView playVideo];
        //返回按钮点击事件回调
        [playerView backButton:^(UIButton *button) {
            NSLog(@"返回按钮被点击");
            //查询是否是全屏状态
            NSLog(@"%d",playerView.isFullScreen);
        }];
        //播放完成回调
        [playerView endPlay:^{
            //销毁播放器
    //        [playerView destroyPlayer];
    //        playerView = nil;
            NSLog(@"播放完成");
        }];
    

    2.TableVIew使用代码

    创建方式和普通页面一样,在创建的时候记录播放器所在cell,在cell滑出当前TableView的时候对播放器进行销毁。

    播放器创建代码
    #pragma mark - 点击播放代理
    - (void)cl_tableViewCellPlayVideoWithCell:(CLTableViewCell *)cell{
        //记录被点击的Cell
        _cell = cell;
        //销毁播放器
        [_playerView destroyPlayer];
        CLPlayerView *playerView = [[CLPlayerView alloc] initWithFrame:CGRectMake(0, 0, cell.CLwidth, cell.CLheight)];
        _playerView = playerView;
        [cell.contentView addSubview:_playerView];
    //    //重复播放,默认不播放
    //    _playerView.repeatPlay = YES;
    //    //当前控制器是否支持旋转,当前页面支持旋转的时候需要设置,告知播放器
    //    _playerView.isLandscape = YES;
    //    //设置等比例全屏拉伸,多余部分会被剪切
    //    _playerView.fillMode = ResizeAspectFill;
    //    //设置进度条背景颜色
    //    _playerView.progressBackgroundColor = [UIColor purpleColor];
    //    //设置进度条缓冲颜色
    //    _playerView.progressBufferColor = [UIColor redColor];
    //    //设置进度条播放完成颜色
    //    _playerView.progressPlayFinishColor = [UIColor greenColor];
    //    //全屏是否隐藏状态栏
    //    _playerView.fullStatusBarHidden = NO;
    //    //转子颜色
    //    _playerView.strokeColor = [UIColor redColor];
        //视频地址
        _playerView.url = [NSURL URLWithString:cell.model.videoUrl];
        //播放
        [_playerView playVideo];
        //返回按钮点击事件回调
        [_playerView backButton:^(UIButton *button) {
            NSLog(@"返回按钮被点击");
        }];
        //播放完成回调
        [_playerView endPlay:^{
            //销毁播放器
            [_playerView destroyPlayer];
            _playerView = nil;
            _cell = nil;
            NSLog(@"播放完成");
        }];
    }
    
    判断cell是否滑出TableView代码
    //cell离开tableView时调用
    - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
        //因为复用,同一个cell可能会走多次
        if ([_cell isEqual:cell]) {
            //区分是否是播放器所在cell,销毁时将指针置空
            [_playerView destroyPlayer];
            _cell = nil;
        }
    }
    

    播放器效果图

    效果图

    总结

    本次主要是修复以前遗留的bug,完善了各种情况的Demo,优化了体验,Demo中有详细的注释,具体请在github下载CLPlayer , 如果喜欢,欢迎star。

    相关文章

      网友评论

      • 85847d1a28f2:这个在4G下好像不能播放,必须要在wifi下才能播放?
      • LLL_0901:在你的demo文件里 测试视频 结束的时候 maskView.slider.value 并不等于1 造成当我播放完再次点击播放的时候会让slider继续跑很短的时间,体验不是很好。 所以我在你播放结束时,调用结束Block之前给slider赋值等于1 由于着急用- - 没有找根本原因,只是暂时性解决办法,希望作者持续更新下去:+1: 加油
        季末微夏:@OddSilent 有空我看看,公司项目比较忙碌
      • 奔跑吧小蚂蚁:你好作者之前一直没有注意今天突然间发现了一个bug:
        打开你在githup的demo 进行播放视频,同时进行内存检测,你会发现第一次进入时,内存是正常的不会出现内存泄漏,你返回上一个界面再次进入进行播放会出现内存泄漏,这个问题就大了。我也在解决中,但是不影响正常的使用,只是内存泄漏了就会存在崩溃的风险。望解决。个人觉得是定时器单例出了问题 但是依然没有解决。
      • 勇敢的呆喵:大佬 我的使用场景是这样的 一个横向滑动的collectionview 里面每个item都是点击之后横屏播放视频的 多点几次 你这个转子就时不时不消失了 底下的slider也不动了 再点点又好了... 能问下啥原因么 我用的是github上最新的代码
        勇敢的呆喵:一直重复点开一个item 横屏播放 只有第一次是正常的 其他的要么是转子不转并且slider不动 要么是状态栏还是竖屏的
      • 奔跑吧小蚂蚁:你好大神我现在特想将定时器这块的东西全部去掉改成获取AVPLayer提供当前播放时间,另外还有优化的地方就是我特别想添加点击进度条快进的功能,另外我觉得CLPLayer完美的地方更是旋转,这块的交互比ZFPlayer要好。
      • 奔跑吧小蚂蚁:CLPlayer 希望大神一直坚持写下去,我一直都想自己写 可是公司给的时间根本不够。自己的惰性又太大了,CLPlayer最人性化的地方就是代码逻辑清晰无论是局部改动,或者整体改动都比较方便,我觉得完全可以在你这套代码基础上进行更深层的优化。大神加油!我可能会把你代码改烂掉,大神不要怪罪~
        奔跑吧小蚂蚁:@季末微夏 最近都被产品搞疯了,直播,视频列表,单视频,还有很多业务界面界面交互也很多,一下子全部要加进去,两个星期的开发时间一个星期测试,三周上,虽然完成了,但是感觉真心累,都感觉快爆毙了,不是自己的代码真心话自己心里都觉得没底,好怀念上家公司,可以按计划的进行学习研究开发……
        季末微夏:@奔跑吧小蚂蚁 大神不敢当,都是在不断学习的道路上,定时器是用来做UI显示加入的,点击进度条快进暂时还没有添加,定时器这一块有优化,还没有更新上去,工作有点忙碌,最近更新不多
      • 奔跑吧小蚂蚁:你好大神 我最近看了你的demo 发现了一个也算是一个bug吧, 当你第一个视频是加载失败,进行切换第二个视频源 你会发现 failButton是不会消失的,另外还有一个问题我记得AVPlayer是提供了当前进度的时间的,为什么还要用自定义的定时器去计算呢?这个我也不会很明白
      • 773f0db0e614:时间有问题
        季末微夏:@LC_ 时间精度问题,等有空修改一下
        773f0db0e614:@季末微夏 00:48-----------00:49,播放完了当前时间慢一秒的样子
        季末微夏:@LC_ 具体是什么问题?请详细说一下,我有空好修改
      • FokHiuFai:修改了系统的横屏样式? 我的项目本来是横屏的 导入之后变竖屏了
        季末微夏:@FokHiuFai 考虑到大部分项目都是竖屏的,所以默认是竖屏的。参考Demo,在父类中重写屏幕旋转方向的几个方法就可以了。
      • 小僧有礼了:请教下,有没有支持flac的框架
      • 午马丶:你好我们的工程不支持全局的旋转如何设置个别的啊,我把demo的 那个全局旋转的取消掉 也不能旋转了,请问怎么解决 啊
        季末微夏:@A丶 播放器采用的是自己所在控制器控制页面旋转,关于页面旋转的,你仔细看看我以前的文章,有详细的demo
        午马丶:@季末微夏 那你的demo里用里其它两种了吗
        季末微夏:参考我以前的文章 https://www.jianshu.com/p/889dfbe93620
      • Kevin_Ray:tableView上点击全屏播放进入全屏播放页之后,点击全屏的返回按妞回到tableView界面,为什么会出现tableView回到顶部呢?
        牵着蜗牛走的我:@季末微夏 额 点击 返回的时候 状态栏 就点到了 这个 尴尬,,
        季末微夏:@牵着蜗牛走的我 手指点到顶部状态条了
        牵着蜗牛走的我:同样的问题 哥们解决没有
      • 博尔茨杰:之前isPlaying知道正在播放,最新版本怎么知道正在播放。
        博尔茨杰:@季末微夏 我的系统是iOS11
        博尔茨杰:@季末微夏 还有一个问题,最新版本点击全屏,发现变成了半屏,之前的版本没有这个问题,麻烦问一下,是因为我其他设置需要特殊配置吗?
        季末微夏:@博尔茨杰 考虑外部使用的可能性不大,就删除了这个接口
      • a115f46b1876:而且 用的你demo暂停后 就不播放,声音也没有了, 但是 我的暂停后声音还有, 怎么回事啊,急
      • a115f46b1876:为什么 把播放器 导入我的工程内,总是布局不合理,太大了, 我完全是按照你的demo做的,为什么总是布局不合理,适配好像有问题啊
      • 季末微夏:@Xcode_6 离开页面的时候销毁播放器没有?你看看什么原因引起的崩溃?
      • Xcode_6:tableview上播放视频 点击导航返回上一级 奔溃了。 奔溃在 MASViewConstraint.m 第130行里 else {
        NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
        }

        怎么解决??
      • 不懂代码的iOS:不支持xcode7.3的吗???
      • Nick_Adams:打开了后台播放功能,做了后台播放的处理。但是按下home键之后,就停止播放,没有走暂停方法。但是锁屏可以。求教。
        季末微夏:@John_Cena666 播放器内部监听了后台相关的事件
        季末微夏:@John_Cena666 #pragma mark - APP活动通知
        - (void)appDidEnterBackground:(NSNotification *)note{
        //将要挂起,停止播放
        [self pausePlay];
        }
        - (void)appDidEnterPlayground:(NSNotification *)note{
        //继续播放
        if (_isUserPlay) {
        [self playVideo];
        }
        }
      • meiqianfangwen:全屏状态下,播放完成后,会回调 endPlay:。此时如何自动退出全屏状态?
        meiqianfangwen:@季末微夏 明白了,谢谢
        季末微夏:没有做接口,是私有的,你修改为公共的就可以了,后期会考虑暴露接口出来

      本文标题:使用AVPlayer自定义支持全屏的播放器(四)

      本文链接:https://www.haomeiwen.com/subject/kuomrxtx.html