美文网首页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