写在前面、感谢作者
使用AVPlayer自定义支持全屏的播放器//www.greatytc.com/p/11e05d684c05
说明:本文使用的是第一篇文章作者的框架,加入第二篇文章作者的思路,实现了 在tabbleViewCell上播放视频、滑动自动播放暂停以及对播放器的定制等等。注意:此处并不包含播放完成cell自动滑动的功能,这一点也不难,相信你学会了滑动自动播放,这一功能也就水到渠成了。
项目效果图
关于制作GIF图的一种方法请跳转
非常遗憾,不满足苹果审核要求,项目迟迟未能上线、不能被大家所下载
2017年7月11日 下午11:37
发件人 Apple
Guideline 3.2.2 - Business - Other Business Model Issues - Unacceptable
The feature in your app displays or promotes third-party apps, which is not appropriate for the App Store.
Please see attached screenshots for details.
Next Steps
To resolve this issue, please remove the feature from your app.
隐藏好像都已经无可救药了
2017年7月13日 下午8:37
发件人 Apple
- 2.2 Business: Other Business Model Issues - Unacceptable
Guideline 3.2.2 - Business - Other Business Model Issues - Unacceptable
Your app displays or promotes third-party apps, which is not appropriate for the App Store.
Next Steps
We encourage you to review your app concept and incorporate different content and features that are in compliance with the App Store Review Guidelines.
对比 作者与我修改后的界面(我只是单纯的修改了一下界面)
作者的界面:
我修改后的界面:
修改地方:
将原来播放暂停的按钮移动到屏幕正中央,在左下角播放暂停按钮处添加一个静音按钮,也就是要么静音要么以某一音量播放(我添加的为0.3)
回到正题:AVPlayer的封装、tableviewcell上视频播放以及滑动播放暂停
实现原理:关于tableviewcell上播放视频的一些说明:首先、我们并不是让每一个cell都拥有一个播放器,而是让tableview所在的控制器拥有一个播放器,然后根据计算来决定哪个cell此时应该播放视频,进而将控制器的播放器 添加 到cell的contentView上。在滑动过程中,我们会不断的判断正在播放的cell是否滑出屏幕或者正在播放的cell需要被另一个cell所替代,通过判断来对播放器的销毁以及重新初始化并添加到相应的cell上。
让控制器拥有一个播放器是我在项目实践过程中想到的一个方案并付诸实践,效果还可以,至少从内存的角度可以看到变化。由原先的 130M左右降到现在的65M左右。
准备工作:下载作者或我的playerView代码
作者GitHubhttps://github.com/JmoVxia/CLPlayer
说明:我会直接使用我项目中的截图来给大家分享,没有一个完整的demo。但是我会教你怎么使用。
1.0关于cell中对于播放器view的布局 以及 cell需要添加一个videoFrame的属性
播放器所在的视图结构大致就是这样。将播放暂停按钮的操作传到tableview所在的控制器,这里有一点需要注意:那就是 计算出 播放器所在的视图也就是上面videoView的frame,传递给控制器以便初始化播放器的时候将其添加到cell上。所以,给每个需要播放视频的cell 添加一个属性,videoFrame,来记录videoView的frame
+ (instancetype)cellWithTableView:(UITableView *)tableView;
@property (nonatomic,strong)ConventionsModel *convention;
//用来记录播放器在cell的frame
@property (nonatomic,assign)CGRect videoFrame;
@property (nonatomic,assign)id<DiscoverVideoCellDelegate> delegate;
@end
在给cell设置数据后并强制布局后再计算videoFrame
- (void)setConvention:(ConventionsModel *)convention
{
_convention = convention;
// 这里给cell上的控件赋值
//如果cell的高度需要自适应,这里一定要强制布局,然后再计算videoFrame,否则、播放器播放时可能不会在你想要的地方
[self layoutIfNeeded];
// convention.cellHeight = CGRectGetMaxY(self.gameIcon.frame) + CGRectGetMinY(self.titleLabel.frame);
// }
self.videoFrame = self.videoView.frame;
}
2.0在控制器中点击播放暂停按钮进行播放 或者 进入控制器后自动播放
理论说明:如果不需要自动播放,那么通过代理将cell上的播放暂停按钮点击事件传递给控制器后,我们只需要 初始化 控制器拥有的那个播放器属性,并将其添加到点击的cell上即可。
在需要自动播放时,我们就需要进行分类判断,当进入该控制器,请求到数据后,播放当前tableview上的第一个(最上面的)视频,当滑动tableview时,播放cell中心(更准确的说:应该是cell上videoView的中心)距离屏幕中心最近的cell上的视频。
下面我直接分享自动播放的情况,在控制器中添加两个属性、一个是播放器(view)一个是正在播放的cell。倘若你的tableview中只有一种cell,你可以直接使用该类型的cell,因为我的项目中并不是每个cell上都有视频播放,所有我直接用UITableViewCell,模态属性,父类指向子类。
@property (nonatomic,strong)CLPlayerView *clPlayerView;
@property (nonatomic,strong)UITableViewCell *playingCell;
3.0请求到数据后播放第一个视频
我们可以在请求到数据的回调里面刷新列表后播放第一个视频
[self.tableView reloadData];
//进入界面播放
[self playVideoInVisiableCells];
3.1进入这个界面就自动播放
//进入这个界面就自动播放
-(void)playVideoInVisiableCells{
//下面是我实际项目中代码。在这里你需要做的就是获得第一个有视频的cell,并进行播放和记录。播放视频可以抽到一个方法中,需要的就是播放视频所在的cell以及视频播放所需要的地址URL等。可以仿照我的进行写代码,当然,如果你的每个cell类型相同且都有视频,你可以直接获取可见的cell NSArray *visiableCells = [self.tableView visibleCells]; 判断 visiableCells.count是否大于0,正在播放的cell就是第一个了。接下来我们说一说播放视频- (void)initPlayerView:(DiscoverVideoCell *)cell playClick:(ConventionsModel *)convention
// 找到下一个要播放的cell(最在屏幕中心的)
DiscoverVideoCell *firstCell = nil;
NSArray *visiableCells = [self.tableView visibleCells];
//存放大框播放视频
for (int i = 0; i < visiableCells.count; i++) {
UITableViewCell *cell = visiableCells[i];
if ([cell isKindOfClass:[DiscoverVideoCell class]]) {
firstCell = (DiscoverVideoCell *)cell;
break;
}
}
//播放第一个视频
[self initPlayerView:firstCell playClick:firstCell.convention];
}
4.0播放视频
初始化视频播放器,并将其添加到正在播放的cell上
- (void)initPlayerView:(DiscoverVideoCell *)cell playClick:(ConventionsModel *)convention
{
self.playingCell = cell;
// self.playingIndexPath = [self.tableView indexPathForCell:cell];
[_clPlayerView destroyPlayer];
_clPlayerView = nil;
CLPlayerView *playerView = [[CLPlayerView alloc] initWithFrame:cell.videoFrame];
_clPlayerView = playerView;
[cell.contentView addSubview:_clPlayerView];
//视频地址
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
_clPlayerView.url = convention.url;
//播放
[_clPlayerView playVideo];
});
//返回按钮点击事件回调
[_clPlayerView backButton:^(UIButton *button) {
NSLog(@"返回按钮被点击");
}];
//播放完成回调
[_clPlayerView endPlay:^{
//销毁播放器
[_clPlayerView destroyPlayer];
_clPlayerView = nil;
NSLog(@"播放完成");
}];
}
5.0滚动播放暂停
//滚动播放暂停说明:对于正在播放的视频、我们需要时刻监听cell是否移除界面;而对于正在播放的cell被替代时,我们可以在滚动停止时进行播放器的初始化并播放。
所以、只需要在以下三个代理方法中实现即可。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//我发现作者对于内存的释放有一点问题,播放器销毁后内存并没有下降
// [_clPlayerView calculateScrollOffset:self.tableView cell:self.playingCell];
//我尝试去修改作者的,似乎也没有起作用,所有自己在改方法中进行销毁
NSArray *cells = [self.tableView visibleCells];
if (![cells containsObject:self.playingCell]) {
if (_clPlayerView) {
//销毁播放器
[_clPlayerView destroyPlayer];
_clPlayerView = nil;
}
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//滑动播放
[self handleScrollPlaying:scrollView];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
if (!decelerate){
//滑动播放
[self handleScrollPlaying:scrollView];
}
}
//这里参考第二篇文章的计算方式,更准确的计算方式应该是:cell中videoView的中心距屏幕的中心最近的cell为正在播放的cell。
//滑动播放
- (void)handleScrollPlaying:(UIScrollView *)scrollView
{
// 找到下一个要播放的cell(最在屏幕中心的)
DiscoverVideoCell *finnalCell = nil;
NSArray *visiableCells = [self.tableView visibleCells];
//存放大框播放视频
NSMutableArray *tempVideoCells = [NSMutableArray array];
for (int i = 0; i < visiableCells.count; i++) {
UITableViewCell *cell = visiableCells[i];
if ([cell isKindOfClass:[DiscoverVideoCell class]]) {
[tempVideoCells addObject:cell];
}
}
NSMutableArray *indexPaths = [NSMutableArray array];
CGFloat gap = MAXFLOAT;
for (DiscoverVideoCell *cell in tempVideoCells) {
[indexPaths addObject:[self.tableView indexPathForCell:cell]];
//计算距离屏幕中心最近的cell
CGPoint coorCentre = [cell.superview convertPoint:cell.center toView:nil];
CGFloat delta = fabs(coorCentre.y-[UIScreen mainScreen].bounds.size.height*0.5);
if (delta < gap) {
gap = delta;
finnalCell = cell;
}
}
// 注意, 如果正在播放的cell和finnalCell是同一个cell, 不应该在播放
if (finnalCell != nil && self.playingCell != finnalCell) {
if (_clPlayerView) {
[_clPlayerView destroyPlayer];
_clPlayerView = nil;
}
[self initPlayerView:finnalCell playClick:finnalCell.convention];
self.playingCell = finnalCell;
return;
}
}
在tableviewcell上播放视频,在这里基本上就结束了。目前在项目中使用良好。