前言

最近在项目中,遇到了视频播放的需求,直接使用系统封装的播放器太过于简单,不能很好的满足项目要求,于是花时间研究了一下,使用AVPlayer来自定义播放器。
    本视频播放器主要自定义了带缓冲显示的进度条,可以拖动调节视频播放进度的播放条,具有当前播放时间和总时间的Label,全屏播放功能,定时消失的工具条。播放器已经封装到UIView中,支持自动旋转切换全屏,支持UITableView

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:812157648,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

主要功能

1.带缓冲显示的进度条

在自定义的时候,主要是需要计算当前进度和监听缓冲的进度,细节方面需要注意进度颜色,进度为0的时候要设置为透明色,缓冲完成的时候需要设置颜色,不然全屏切换就会导致缓冲完成的进度条颜色消失。

  • 自定义进度条的代码

#pragma mark - 创建UIProgressView
- (void)createProgress
{CGFloat width;if (_isFullScreen == NO){width = self.frame.size.width;}else{width = self.frame.size.height;}_progress                = [[UIProgressView alloc]init];_progress.frame          = CGRectMake(_startButton.right + Padding, 0, width - 80 - Padding - _startButton.right - Padding - Padding, Padding);_progress.centerY        = _bottomView.height/2.0;//进度条颜色_progress.trackTintColor = ProgressColor;// 计算缓冲进度NSTimeInterval timeInterval = [self availableDuration];CMTime duration             = _playerItem.duration;CGFloat totalDuration       = CMTimeGetSeconds(duration);[_progress setProgress:timeInterval / totalDuration animated:NO];CGFloat time  = round(timeInterval);CGFloat total = round(totalDuration);//确保都是numberif (isnan(time) == 0 && isnan(total) == 0){if (time == total){//缓冲进度颜色_progress.progressTintColor = ProgressTintColor;}else{//缓冲进度颜色_progress.progressTintColor = [UIColor clearColor];}}else{//缓冲进度颜色_progress.progressTintColor = [UIColor clearColor];}[_bottomView addSubview:_progress];
}
  • 缓冲进度计算和监听代码

#pragma mark - 缓存条监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{if ([keyPath isEqualToString:@"loadedTimeRanges"]){// 计算缓冲进度NSTimeInterval timeInterval = [self availableDuration];CMTime duration             = _playerItem.duration;CGFloat totalDuration       = CMTimeGetSeconds(duration);[_progress setProgress:timeInterval / totalDuration animated:NO];//设置缓存进度颜色_progress.progressTintColor = ProgressTintColor;}
}

2.可以拖动调节视频播放进度的播放条

这里主要需要注意的是创建的播放条需要比进度条稍微长一点,这样才可以看到滑块从开始到最后走完整个进度条。播放条最好单独新建一个继承自UISlider的控件,因为进度条和播放条的大小很可能不能完美的重合在一起,这样看起来就会有2条线条,很不美观,内部代码将其默认长度和起点重新布局。

  • 播放条控件内部代码
        这里重写- (CGRect)trackRectForBounds:(CGRect)bounds方法,才能改变播放条的大小。
// 控制slider的宽和高,这个方法才是真正的改变slider滑道的高的
- (CGRect)trackRectForBounds:(CGRect)bounds
{[super trackRectForBounds:bounds];return CGRectMake(-2, (self.frame.size.height - 2.6)/2.0, CGRectGetWidth(bounds) + 4, 2.6);
}
  • 创建播放条代码
#pragma mark - 创建UISlider
- (void)createSlider
{_slider         = [[Slider alloc]init];_slider.frame   = CGRectMake(_progress.x, 0, _progress.width, ViewHeight);_slider.centerY = _bottomView.height/2.0;[_bottomView addSubview:_slider];//自定义滑块大小UIImage *image     = [UIImage imageNamed:@"round"];//改变滑块大小UIImage *tempImage = [image OriginImage:image scaleToSize:CGSizeMake( SliderSize, SliderSize)];//改变滑块颜色UIImage *newImage  = [tempImage imageWithTintColor:SliderColor];[_slider setThumbImage:newImage forState:UIControlStateNormal];//开始拖拽[_slider addTarget:selfaction:@selector(processSliderStartDragAction:)forControlEvents:UIControlEventTouchDown];//拖拽中[_slider addTarget:selfaction:@selector(sliderValueChangedAction:)forControlEvents:UIControlEventValueChanged];//结束拖拽[_slider addTarget:selfaction:@selector(processSliderEndDragAction:)forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];//左边颜色_slider.minimumTrackTintColor = PlayFinishColor;//右边颜色_slider.maximumTrackTintColor = [UIColor clearColor];
}
  • 拖动播放条代码

#pragma mark - 拖动进度条
//开始
- (void)processSliderStartDragAction:(UISlider *)slider
{//暂停[self pausePlay];[_timer invalidate];
}
//结束
- (void)processSliderEndDragAction:(UISlider *)slider
{//继续播放[self playVideo];_timer = [NSTimer scheduledTimerWithTimeInterval:DisappearTimetarget:selfselector:@selector(disappear)userInfo:nilrepeats:NO];
}
//拖拽中
- (void)sliderValueChangedAction:(UISlider *)slider
{//计算出拖动的当前秒数CGFloat total           = (CGFloat)_playerItem.duration.value / _playerItem.duration.timescale;NSInteger dragedSeconds = floorf(total * slider.value);//转换成CMTime才能给player来控制播放进度CMTime dragedCMTime     = CMTimeMake(dragedSeconds, 1);[_player seekToTime:dragedCMTime];
}

3.具有当前播放时间和总时间的Label

创建时间显示Label的时候,我们需要创建一个定时器,每秒执行一下代码,来实现动态改变Label上的时间显示。

  • Label创建代码
#pragma mark - 创建播放时间
- (void)createCurrentTimeLabel
{_currentTimeLabel           = [[UILabel alloc]init];_currentTimeLabel.frame     = CGRectMake(0, 0, 80, Padding);_currentTimeLabel.centerY   = _progress.centerY;_currentTimeLabel.right     = _backView.right - Padding;_currentTimeLabel.textColor = [UIColor whiteColor];_currentTimeLabel.font      = [UIFont systemFontOfSize:12];_currentTimeLabel.text      = @"00:00/00:00";[_bottomView addSubview:_currentTimeLabel];
}
  • Label上面定时器的定时事件
#pragma mark - 计时器事件
- (void)timeStack
{if (_playerItem.duration.timescale != 0){//总共时长_slider.maximumValue = 1;//当前进度_slider.value        = CMTimeGetSeconds([_playerItem currentTime]) / (_playerItem.duration.value / _playerItem.duration.timescale);//当前时长进度progressNSInteger proMin     = (NSInteger)CMTimeGetSeconds([_player currentTime]) / 60;//当前秒NSInteger proSec     = (NSInteger)CMTimeGetSeconds([_player currentTime]) % 60;//当前分钟//duration 总时长NSInteger durMin     = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale / 60;//总秒NSInteger durSec     = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale % 60;//总分钟self.currentTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld / %02ld:%02ld", (long)proMin, proSec, durMin, durSec];}//开始播放停止转子if (_player.status == AVPlayerStatusReadyToPlay){[_activity stopAnimating];}else{[_activity startAnimating];}}

4.全屏播放功能

上面都是一些基本功能,最重要的还是全屏功能的实现。全屏功能这里多说一下,由于我将播放器封装到一个UIView里边,导致在做全屏的时候出现了一些问题。因为播放器被封装起来了,全屏的时候,播放器的大小就很可能超出父类控件的大小范围,造成了超出部分点击事件无法获取,最开始打算重写父类-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法,但是想到这样做就没有达到封装的目的,于是改变了一下思路,在全屏的时候,将播放器添加到Window上,这样播放器就不会超出父类的范围大小,小屏的时候将播放器从Window上还原到原有的父类上。

  • 全屏代码
        全屏的适配采用的是遍历删除原有控件,重新布局创建全屏控件的方法实现。
#pragma mark - 全屏按钮响应事件
- (void)maxAction:(UIButton *)button
{if (_isFullScreen == NO){[self fullScreenWithDirection:Letf];}else{[self originalscreen];}
}
#pragma mark - 全屏
- (void)fullScreenWithDirection:(Direction)direction
{//记录播放器父类_fatherView = self.superview;_isFullScreen = YES;//取消定时消失[_timer invalidate];[self setStatusBarHidden:YES];//添加到Window上[self.window addSubview:self];if (direction == Letf){[UIView animateWithDuration:0.25 animations:^{self.transform = CGAffineTransformMakeRotation(M_PI / 2);}];}else{[UIView animateWithDuration:0.25 animations:^{self.transform = CGAffineTransformMakeRotation( - M_PI / 2);}];}self.frame         = CGRectMake(0, 0, ScreenWidth, ScreenHeight);_playerLayer.frame = CGRectMake(0, 0, ScreenHeight, ScreenWidth);//删除原有控件[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];//创建全屏UI[self creatUI];
}
#pragma mark - 原始大小
- (void)originalscreen
{_isFullScreen = NO;//取消定时消失[_timer invalidate];[self setStatusBarHidden:NO];[UIView animateWithDuration:0.25 animations:^{//还原大小self.transform = CGAffineTransformMakeRotation(0);}];self.frame = _customFarme;_playerLayer.frame = CGRectMake(0, 0, _customFarme.size.width, _customFarme.size.height);//还原到原有父类上[_fatherView addSubview:self];//删除[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];//创建小屏UI[self creatUI];
}
  • 创建播放器UI的代码
#pragma mark - 创建播放器UI
- (void)creatUI
{//最上面的View_backView                 = [[UIView alloc]init];_backView.frame           = CGRectMake(0, _playerLayer.frame.origin.y, _playerLayer.frame.size.width, _playerLayer.frame.size.height);_backView.backgroundColor = [UIColor clearColor];[self addSubview:_backView];//顶部View条_topView                 = [[UIView alloc]init];_topView.frame           = CGRectMake(0, 0, _backView.width, ViewHeight);_topView.backgroundColor = [UIColor colorWithRed:0.00000f green:0.00000f blue:0.00000f alpha:0.50000f];[_backView addSubview:_topView];//底部View条_bottomView                 = [[UIView alloc] init];_bottomView.frame           = CGRectMake(0, _backView.height - ViewHeight, _backView.width, ViewHeight);_bottomView.backgroundColor = [UIColor colorWithRed:0.00000f green:0.00000f blue:0.00000f alpha:0.50000f];[_backView addSubview:_bottomView];// 监听loadedTimeRanges属性[_playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];//创建播放按钮[self createButton];//创建进度条[self createProgress];//创建播放条[self createSlider];//创建时间Label[self createCurrentTimeLabel];//创建返回按钮[self createBackButton];//创建全屏按钮[self createMaxButton];//创建点击手势[self createGesture];//计时器,循环执行[NSTimer scheduledTimerWithTimeInterval:1.0ftarget:selfselector:@selector(timeStack)userInfo:nilrepeats:YES];//定时器,工具条消失_timer = [NSTimer scheduledTimerWithTimeInterval:DisappearTimetarget:selfselector:@selector(disappear)userInfo:nilrepeats:NO];}

5.定时消失的工具条

如果工具条是显示状态,不点击视频,默认一段时间后,自动隐藏工具条,点击视频,直接隐藏工具条;如果工具条是隐藏状态,点击视频,就让工具条显示。功能说起来很简单,最开始的时候,我使用GCD延迟代码实现,但是当点击让工具条显示,然后再次点击让工具条消失,多点几下你会发现你的定时消失时间不对。这里我们需要注意的是,当你再次点击的时候需要取消上一次的延迟执行代码,才能够让下一次点击的时候,延迟代码正确执行。这里采用定时器来实现,因为定时器可以取消延迟执行的代码。

  • 点击视频的代码
#pragma mark - 轻拍方法
- (void)tapAction:(UITapGestureRecognizer *)tap
{//取消定时消失[_timer invalidate];if (_backView.alpha == 1){[UIView animateWithDuration:0.5 animations:^{_backView.alpha = 0;}];}else if (_backView.alpha == 0){//添加定时消失_timer = [NSTimer scheduledTimerWithTimeInterval:DisappearTimetarget:selfselector:@selector(disappear)userInfo:nilrepeats:NO];[UIView animateWithDuration:0.5 animations:^{_backView.alpha = 1;}];}
}

接口与用法

这里是写给懒人看的,对播放器做了一下简单的封装,留了几个常用的接口,方便使用。

  • 接口

/**视频url*/
@property (nonatomic,strong) NSURL *url;
/**旋转自动全屏,默认Yes*/
@property (nonatomic,assign) BOOL autoFullScreen;
/**重复播放,默认No*/
@property (nonatomic,assign) BOOL repeatPlay;
/**是否支持横屏,默认No*/
@property (nonatomic,assign) BOOL isLandscape;
/**播放*/
- (void)playVideo;
/**暂停*/
- (void)pausePlay;
/**返回按钮回调方法*/
- (void)backButton:(BackButtonBlock) backButton;
/**播放完成回调*/
- (void)endPlay:(EndBolck) end;
/**销毁播放器*/
- (void)destroyPlayer;
/**根据播放器所在位置计算是否滑出屏幕,@param tableView Cell所在tableView@param cell 播放器所在Cell@param beyond 滑出后的回调*/
- (void)calculateWith:(UITableView *)tableView cell:(UITableViewCell *)cell beyond:(BeyondBlock) beyond;
  • 使用方法

直接使用cocoapods导入,pod 'CLPlayer'

  • 具体使用代码
CLPlayerView *playerView = [[CLPlayerView alloc] initWithFrame:CGRectMake(0, 90, ScreenWidth, 300)];
[self.view addSubview:playerView];
//根据旋转自动支持全屏,默认支持
//    playerView.autoFullScreen = NO;
//重复播放,默认不播放
//    playerView.repeatPlay     = YES;
//如果播放器所在页面支持横屏,需要设置为Yes,不支持不需要设置(默认不支持)
//    playerView.isLandscape    = YES;//视频地址
playerView.url = [NSURL URLWithString:@"http://wvideo.spriteapp.cn/video/2016/0215/56c1809735217_wpd.mp4"];//播放
[playerView playVideo];//返回按钮点击事件回调
[playerView backButton:^(UIButton *button) {NSLog(@"返回按钮被点击");
}];//播放完成回调
[playerView endPlay:^{//销毁播放器[playerView destroyPlayer];playerView = nil;NSLog(@"播放完成");
}];

说明

UIImage+TintColor是用来渲染图片颜色的分类,由于缺少图片资源,所以采用其他颜色图片渲染成自己需要的颜色;UIImage+ScaleToSize这个分类是用来改变图片尺寸大小的,因为播放条中的滑块不能直接改变大小,所以通过改变图片尺寸大小来控制滑块大小;UIView+SetRect是用于适配的分类。

总结

在自定义播放器的时候,需要注意的细节太多,这里就不一一细说了,更多细节请看Demo,Demo中有很详细的注释。考虑到大部分APP不支持横屏,播放器默认是不支持横屏的,如果需要支持横屏(勾选了支持左右方向),创建播放器的时候,写上这句代码playerView.isLandscape = YES;

播放器效果图

Demo地址

最近更新修改了很多地方的代码,主要是使用Masonry来重构了一下工具条,修复了一些bug,具体还请参考CLPlayer
如果喜欢,欢迎star。

作者:季末微夏
链接:https://www.jianshu.com/p/b9659492d064

使用AVPlayer自定义支持全屏的播放器(一)相关推荐

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

    前言     前段时间封装了一个视频播放器,由于时间匆忙,还有很多问题以及细节还没有来得及修改,最近挤了一点时间,将播放器完善了一下,具体思路请参考使用AVPlayer自定义支持全屏的播放器(一),本 ...

  2. H5页面 点击按钮播放视频,默认全屏播放,取消全屏后播放器自动隐藏

    /*** @description 播放全屏 * @param {Object} element*/function launchFullScreen(element) {if (element.re ...

  3. jquery video全屏_video播放器全屏兼容方案

    播放视频 video全屏 var Features = {}; var target = $('#video')[0]; // Get DOM element from jQuery collecti ...

  4. android视频播放器ui,ArtVideoPlayer 一个灵活的Android视频播放器,支持全屏,小屏播放...

    ArtPlayer 简介 Kotlin实现的视频播放器,将MediaPlayer与VideoView解耦合,支持切换播放器内核(如ExoPlayer和ijkPlayer),支持自定义控制视图,提供Me ...

  5. android studio多媒体播放器,Android支持全屏、小窗口的视频播放器

    NiceVieoPlayer Android支持全屏.小窗口的视频播放器,完美实现全屏.小窗口播放切换 Features 用IjkPlayer/MediaPlayer + TextureView封装, ...

  6. vue自定义echart 全屏工具

    需求描述 如图:在动态加载图表的情况下,自定义一个全屏按钮,点击按钮全屏显示图表. 实现效果 实现思路: 遮罩:定义两个div:a和A,a为当前图表,A为全屏后的图表,全屏后A在a的上层显示,退出全屏 ...

  7. Elmedia Player - Mac 上最好用的音视屏媒体播放器

    Elmedia Player - Mac 上最好用的音视屏媒体播放器 Elmedia是macOS的富媒体播放器,支持多种文件格式,包括大多数视频和音频.一些支持的文件类型包括FLV, SWF, WMV ...

  8. 树莓派全语音控制媒体播放器(Fully Speech-Controlled Media Player)

    树莓派全语音控制媒体播放器(Fully Speech-Controlled Media Player) 主要特点: *全程无外设操作,可以在无鼠标键盘显示器等外设的情况下(Headless),完全通过 ...

  9. 用jq和bootstrap3 实现一个自定义网页版的音乐播放器

    用jq和bootstrap3 实现一个自定义网页版的音乐播放器 1.主要实现功能 1.1.点击播放与暂停,上一首和下一首: 注:用python返回所有歌曲的信息,加载完成默认选择第一首歌曲,通过传递歌 ...

最新文章

  1. 股票移动平均线matlab,股票的移动平均线 (图文)
  2. CSW:闪电网络是一种证券,BCH避开了它
  3. Linux命令:sed
  4. 对进入单用户进行加密
  5. [C++] 用Xcode来写C++程序[6] Name visibility
  6. ai不同形状的拼版插件_AI矩形/异型自动排料插件AINester 16.0(支持Illustrator CC 2015/2017)...
  7. Android Native 内存泄漏系统化解决方案
  8. 架构师需要懂的环境配置标准化
  9. es6 模块的整体加载
  10. 如何快速学会java编程?
  11. visual怎么设置默认运行_神马?游戏和软件不能运行?来3DM一下吧!
  12. Java实现冒泡排序及逆序冒泡排序
  13. python基于OpenCV模块实现视频流数据切割为图像帧数据
  14. Windows编译OpenCV
  15. 算法笔记:使用A*算法解决八数码问题
  16. DB2活动日志占用过大
  17. html怎么设置seo,简单说明一下html相关的seo设置!
  18. 【英语学习】【医学】生物化学(biochemistry)系统
  19. 【ARMv8】异常级别的定义EL0、EL1、EL2、EL3
  20. Python GUI之tkinter的皮肤(ttkbootstrap)打造出你的窗口之美

热门文章

  1. 杰理之FLASH-OTP区域使用方法【篇】
  2. 人工智能数学基础:两个存在映射关系的随机变量的概率密度函数关系研究
  3. nagios 安装脚本
  4. linux脚本编写计算器,Shell中编写简单计算器
  5. CMake安装、配置编译C++代码(Mac、Linux)————附带详细步骤和代码
  6. 客流统计大揭秘——各种客流
  7. BBS论坛系统的设计与实现
  8. Text Template Parser(多源数据提取软件)官方正式版V2.5 | 数据提取软件有哪些?
  9. [心情]其实我只想要一份稳定有发展潜力的工作,为公司做出最大的贡献之余也丰富我的人生阅历的工作而已从而达到我人生的巅峰...
  10. C#读取dbf数据或者Excel转为DataTable