前言
前段时间封装了一个视频播放器使用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;
}
新增功能
新增加了转子动画,增加拖拽后转子衔接动画,增加各类接口。
转子动画
利用CAShapeLayer
和UIBezierPath
做了一个简单的加载动画。
代码
@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。
网友评论
打开你在githup的demo 进行播放视频,同时进行内存检测,你会发现第一次进入时,内存是正常的不会出现内存泄漏,你返回上一个界面再次进入进行播放会出现内存泄漏,这个问题就大了。我也在解决中,但是不影响正常的使用,只是内存泄漏了就会存在崩溃的风险。望解决。个人觉得是定时器单例出了问题 但是依然没有解决。
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
怎么解决??
- (void)appDidEnterBackground:(NSNotification *)note{
//将要挂起,停止播放
[self pausePlay];
}
- (void)appDidEnterPlayground:(NSNotification *)note{
//继续播放
if (_isUserPlay) {
[self playVideo];
}
}