这次自定义一个视频播放器
先看效果:
功能如下:
1播放视频 (感觉是废话)
2播放暂停
3放大缩小
4工具条自动隐藏,点击显示
5进度条随时间移动
6点击,拖拽移动条显示不同数据
前四个比较简单放到一起讲,顺带把滑动条也加上
先引用
import <AVFoundation/AVFoundation.h>
需要注册的属性
// 视屏播放器背景图
@property (strong, nonatomic) UIView *backView;
// 播放视频控件
@property (nonatomic,strong) AVPlayer *player;
// 提供视频信息 创建AVPlayer使用的
@property (nonatomic,strong) AVPlayerItem *playerItem;
// 给AVPlayer一个播放的layer层
@property (nonatomic,strong) AVPlayerLayer *playerLayer;
// 工具栏 view (放播放按钮等控件的view)
@property (nonatomic,strong) UIView *bottomView;
// 播放
@property (nonatomic,strong) UIButton *playButton;
// 全屏按钮
@property (nonatomic,strong) UIButton *fullScreenButton;
// 滑动条
@property (nonatomic,strong) UISlider *slider;
上代码
// 初始化背景
self.backView = [[UIView alloc] initWithFrame:CGRectMake(0, 100, kScreenWidth, kScreenHeight / 2.5)];
self.backView.backgroundColor = [UIColor clearColor];
[self.view addSubview:self.backView];
// 提供视频信息 创建AVPlayer使用的
self.playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:@"http://static.tripbe.com/videofiles/20121214/9533522808.f4v.mp4"]];
// 通过AVPlayerItem创建AVPlayer
self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
// 单纯使用AVPlayer类是无法显示视频的,要将视频层添加至AVPlayerLayer中,这样才能将视频显示出来 所以要给AVPlayer一个播放的layer层
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
// 位置
_playerLayer.frame = self.backView.bounds;
// 插入指定位置 第二个参数,可以将一个图层插入到指定的下标位置
[self.backView.layer insertSublayer:self.playerLayer atIndex:0];
//添加手势动作,隐藏下面的进度条
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
[self.backView addGestureRecognizer:tap];
// 布局底部功能栏
self.bottomView = [[UIView alloc] init];
// 透明度
self.bottomView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
[self.backView addSubview:self.bottomView];
[self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.backView).with.offset(0);
make.right.equalTo(self.backView).with.offset(0);
make.bottom.equalTo(self.backView).with.offset(0);
make.height.mas_equalTo(30);
}];
// 播放或暂停按钮
self.playButton = [UIButton buttonWithType:UIButtonTypeCustom];
// 选中状态
self.playButton.selected = YES;
[self.playButton setImage:[UIImage imageNamed:@"pause"] forState:UIControlStateNormal];
[self.bottomView addSubview:self.playButton];
[self.playButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.bottomView).with.offset(5);
make.centerY.equalTo(self.bottomView);
make.size.mas_equalTo(CGSizeMake(30, 30));
}];
// 添加点击播放事件
[self.playButton addTarget:self action:@selector(pauseOrPlay:) forControlEvents:UIControlEventTouchUpInside];
// 底部全屏按钮
self.fullScreenButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.fullScreenButton setImage:[UIImage imageNamed:@"fullscreen"] forState:UIControlStateNormal];
self.fullScreenButton.selected = YES;
[self.bottomView addSubview:self.fullScreenButton];
[self.fullScreenButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.bottomView).with.offset(-5);
make.centerY.equalTo(self.bottomView);
make.size.mas_equalTo(CGSizeMake(30, 30));
}];
// 添加点击全屏事件
[self.fullScreenButton addTarget:self action:@selector(clickFullScreen:) forControlEvents:UIControlEventTouchUpInside];
// 底部进度条
self.slider = [[UISlider alloc] init];
// 已播放的部分颜色
self.slider.minimumTrackTintColor = [UIColor whiteColor];
// 未播放部分的进度条颜色
self.slider.maximumTrackTintColor = [UIColor grayColor];
// 初始位置 默认是0
self.slider.value = 0.0;
[self.slider setThumbImage:[UIImage imageNamed:@"dot"] forState:UIControlStateNormal];
[self.bottomView addSubview:self.slider];
[self.slider mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.bottomView).with.offset(45);
make.right.equalTo(self.bottomView).with.offset(-45);
make.centerY.equalTo(self.bottomView);
}];
// 播放视频
[self.player play];
布局基本就全了,在添加上对应的点击事件
#pragma mark ==== 暂停或者播放
- (void)pauseOrPlay:(UIButton *)sender{
if (sender.selected == YES)
{
[sender setImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];
// 暂停
[self.player pause];
}
else
{
[sender setImage:[UIImage imageNamed:@"pause"] forState:UIControlStateNormal];
[self.player play];
}
// 别忘记切换 状态
sender.selected = !sender.selected;
}
#pragma mark ==== 点击全屏按钮
- (void)clickFullScreen:(UIButton *)button{
if (button.selected == YES)
{ // 全屏
[self toFullScreenWithInterfaceOrientation:UIInterfaceOrientationLandscapeLeft];
[self.fullScreenButton setImage:[UIImage imageNamed:@"nonfullscreen@3x"] forState:UIControlStateNormal];
}
else
{
// 缩小
[self toSmallScreen];
[self.fullScreenButton setImage:[UIImage imageNamed:@"fullscreen@3x"] forState:UIControlStateNormal];
}
button.selected = !button.selected;
}
#pragma mark ==== 全屏显示
- (void)toFullScreenWithInterfaceOrientation:(UIInterfaceOrientation )interfaceOrientation{
if (interfaceOrientation==UIInterfaceOrientationLandscapeLeft) {
// 选择屏幕
self.backView.transform = CGAffineTransformMakeRotation(-M_PI_2);
}
// BackView 全屏
self.backView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
// layer的方向宽和高对调
self.playerLayer.frame = CGRectMake(0, 0, kScreenHeight, kScreenWidth);
[self.bottomView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(50);
make.top.mas_equalTo(kScreenWidth-50);
make.left.equalTo(self.backView).with.offset(0);
make.width.mas_equalTo(kScreenHeight);
}];
// 加到window上面
[[UIApplication sharedApplication].keyWindow addSubview:self.backView];
}
#pragma mark ==== 缩小
- (void)toSmallScreen{
self.backView.transform = CGAffineTransformIdentity;
self.backView.frame = CGRectMake(0, 100, kScreenWidth, kScreenHeight / 2.5);
self.playerLayer.frame = self.backView.bounds;
[self.view addSubview:self.backView];
[self.bottomView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.backView).with.offset(0);
make.right.equalTo(self.backView).with.offset(0);
make.height.mas_equalTo(50);
make.bottom.equalTo(self.backView).with.offset(0);
}];
}
#pragma mark ==== 隐藏 显示 底部 工具栏
- (void)singleTap:(UITapGestureRecognizer *)tap{
[UIView animateWithDuration:1.0 animations:^{
if (self.bottomView.alpha == 1)
{
self.bottomView.alpha = 0;
}
else if (self.bottomView.alpha == 0)
{
self.bottomView.alpha = 1;
}
}];
}
到此前四项只有4的自动隐藏还没有完成
把功能拆开想:他不是立刻隐藏,而是停顿了一会再隐藏的,这需要计时器
来设置时间,看视频播放软件暂停的时候是不隐藏
的,那就是需要播放了一定时间再隐藏,这就需要监听播放器
先写个时间属性,基于安全考虑设为属性好释放
// 自动消失定时器
@property (nonatomic,strong) NSTimer *autoDismissTimer;
监听视频播放器,用KVO实现
// 监听播放器状态变化 利用KVO 监听 只要属性一改变就会调用监听方法 暂停时不会更新,所以暂停不会继续调用通知
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
对应的监听方法
#pragma mark ==== 监听播放器的变化属性
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
// 播放时
if ([keyPath isEqualToString:@"status"])
{
// 播放器状态
AVPlayerItemStatus statues = [change[NSKeyValueChangeNewKey] integerValue];
switch (statues) {
case AVPlayerItemStatusReadyToPlay:
// 滑动条最大值 = 持续时间(播放总时间)
self.slider.maximumValue = CMTimeGetSeconds(self.playerItem.duration);
// 随时间移动
[self initTimer];
// 计时器为空时创建计时器
if (!self.autoDismissTimer)
{
// 8秒执行一次 自动隐藏底栏
self.autoDismissTimer = [NSTimer timerWithTimeInterval:8.0 target:self selector:@selector(autoDismissView:) userInfo:nil repeats:YES];
// 主线程 不需要 return
[[NSRunLoop currentRunLoop] addTimer:self.autoDismissTimer forMode:NSDefaultRunLoopMode];
}
break;
case AVPlayerItemStatusUnknown:
// 未知状态
break;
case AVPlayerItemStatusFailed:
// 播放失败
break;
default:
break;
}
}
}
//调用plaer进行页面更新
- (void)initTimer
{
//player的定时器
__weak typeof(self)weakSelf = self;
// 每秒更新一次UI Slider CMTimeMake(表示 当前视频播放到的第几桢数,每秒的帧数)
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
weakSelf.slider.value = CMTimeGetSeconds(weakSelf.playerItem.currentTime);
}];
}
//自动隐藏底部功能栏
- (void)autoDismissView:(NSTimer *)timer{
// 0 暂停 1时正在播放视频
if (self.player.rate == 1)
{
[UIView animateWithDuration:2.0 animations:^{
self.bottomView.alpha = 0;
}];
}
}
现在只剩下6了
拆开分析,点击播放应当是需要点击手势
的,拖动还是用监听
比较好
点击手势
// 点击
UITapGestureRecognizer *tapSlider = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(touchSlider:)];
[self.slider addGestureRecognizer:tapSlider];
[self.bottomView addSubview:self.slider];
拖动监听
[self.slider addTarget:self action:@selector(sliderTapValueChange:) forControlEvents:UIControlEventTouchUpInside];
对应的方法
#pragma mark ==== 点击调用 或者 拖拽完毕的时候调用
- (void)sliderTapValueChange:(UISlider *)slider
{
// seektotime 获取精准定位
// 直接用秒来获取CMTime
// 当前视频播放到的帧数的具体时间,每秒的帧数
[self.player seekToTime:CMTimeMakeWithSeconds(slider.value, self.playerItem.currentTime.timescale)];
}
#pragma mark ==== 点击Slider
- (void)touchSlider:(UITapGestureRecognizer *)tap
{
// 根据点击的坐标计算对应的比例
CGPoint touch = [tap locationInView:self.slider];
// 占总厂的比例
CGFloat scale = touch.x / self.slider.bounds.size.width;
//CMTimeGetSeconds 获取时间的秒数
self.slider.value = CMTimeGetSeconds(self.playerItem.duration) * scale;
// 精准 具体时间,每秒的帧数
[self.player seekToTime:CMTimeMakeWithSeconds(self.slider.value, self.playerItem.currentTime.timescale)];
}
再考虑一种情况暂停时拖拽和点击时
- (void)touchSlider:(UITapGestureRecognizer *)tap
{
// 根据点击的坐标计算对应的比例
CGPoint touch = [tap locationInView:self.slider];
// 占总厂的比例
CGFloat scale = touch.x / self.slider.bounds.size.width;
//CMTimeGetSeconds 获取时间的秒数
self.slider.value = CMTimeGetSeconds(self.playerItem.duration) * scale;
// 精准 具体时间,每秒的帧数
[self.player seekToTime:CMTimeMakeWithSeconds(self.slider.value, self.playerItem.currentTime.timescale)];
if (self.player.rate != 1)
{
// 1的时候播放 0暂停
[self.playButton setImage:[UIImage imageNamed:@"pause"] forState:UIControlStateNormal];
[self.player play];
}
}
这样就可以了
到此自定义AVPlayer视频播放器功能已全部实现
附上下载地址
https://gitee.com/wwwzz/ZiDingYiAVPlayerShiPinBoFangQi.git
网友评论