使用AVPlayer自定义支持全屏的播放器(一)
来源:互联网 发布:单片机指令 编辑:程序博客网 时间:2024/06/07 04:01
前言
最近在项目中,遇到了视频播放的需求,直接使用系统封装的播放器太过于简单,不能很好的满足项目要求,于是花时间研究了一下,使用AVPlayer
来自定义播放器。
本视频播放器主要自定义了带缓冲显示的进度条,可以拖动调节视频播放进度的播放条,具有当前播放时间和总时间的Label,全屏播放功能,定时消失的工具条。播放器已经封装到UIView
中,支持自动旋转切换全屏,支持UITableView
。
主要功能
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); //确保都是number if (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:self action:@selector(processSliderStartDragAction:) forControlEvents:UIControlEventTouchDown]; //拖拽中 [_slider addTarget:self action:@selector(sliderValueChangedAction:) forControlEvents:UIControlEventValueChanged]; //结束拖拽 [_slider addTarget:self action:@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:DisappearTime target:self selector:@selector(disappear) userInfo:nil repeats: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); //当前时长进度progress NSInteger 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.0f target:self selector:@selector(timeStack) userInfo:nil repeats:YES]; //定时器,工具条消失 _timer = [NSTimer scheduledTimerWithTimeInterval:DisappearTime target:self selector:@selector(disappear) userInfo:nil repeats: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:DisappearTime target:self selector:@selector(disappear) userInfo:nil repeats: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', '~> 1.0.0'
- 具体使用代码
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地址
后续会有持续更新,地址---->CLPlayer
如果喜欢,欢迎star。
原文链接:http://www.jianshu.com/p/b9659492d064
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
- 使用AVPlayer自定义支持全屏的播放器(一)
- 使用AVPlayer自定义支持全屏的播放器(二)
- ios 使用AVPlayer自定义视频播放器
- ios 使用AVPlayer自定义视频播放器
- CYC-AVPlayer播放器的简单使用
- AVPlayer自定义视频播放器
- 基于AVPlayer 自定义播放器
- 基于 AVPlayer 自定义播放器
- iOS中使用AVPLayer自定义视频播放器
- 视频播放AVPlayer的使用
- iOS AVPlayer支持播放的格式
- iOS 使用AVPlayer自定义的播放器,当手机在静音模式播放器也静音的解决方法!
- iOS基于AVPlayer自定义播放器
- 视频播放器(AVPlayer)
- 基于AVPlayer的音乐播放器,支持上下曲,随机播放,后台播...
- AVPlayer视频播放的使用简介
- 使用AVPlayer播放视频
- 基于AVPlayer简单封装的播放器
- 干货来了!2016年DevExpress资源汇总(更新、文档)
- String处理
- LeetCode笔记:234. Palindrome Linked List
- XMPP(三)-安卓即时通讯客户端
- 深入Android MediaPlayer的使用方法详解
- 使用AVPlayer自定义支持全屏的播放器(一)
- Oracle之锁排查以及解决
- 用PL/SQL developer 连接Oracle服务器报错ORA-12537
- vue过滤器在v2.0版本用法
- iOS 导航栏的那些事儿
- 编译使遇到的错误以及xcode8在ios8.0下真机调试
- Swift-面向对象,元组,重写,继承,构造,可选值
- HashMap一键对多值的存与取
- Jquery一个简单的注册验证