最近要再app里加一个视频的模块来放广告,功能要有播放,暂停,静音,然后能拖进度条,虽然不知道一个用来放广告的视频为什么要有快进这种功能,但是给了设计给了图那就开始动手吧,因为功能也不是很多,所以就自己封装好了。
1.封装对外的方法和属性
因为AVPlayer是属于AVFoundation框架,开始时候自然要导入框架
#import <AVFoundation/AVFoundation.h>
方法倒也不用封装太多,主要是一个传入视频链接的方法
//传入视频地址
-(void)updatePlayerWithURL:(NSURL *)url;
2.搭建播放器界面
这个,纯代码写一下吧,先把播放器的界面搭建出来。
#pragma mark - 构造方法
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
//是否展示工具栏的开关
_isShowToolbar = NO;
//播放器的view
UIView *playerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
self.playerView = playerView;
[self addSubview:playerView];
// 设置AVPlayer
self.player = [[AVPlayer alloc] init];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
_playerLayer.frame = self.bounds;
/*
AVLayerVideoGravityResize, // 非均匀模式。两个维度完全填充至整个视图区域
AVLayerVideoGravityResizeAspect, // 等比例填充,直到一个维度到达区域边界
AVLayerVideoGravityResizeAspectFill, // 等比例填充,直到填充满整个视图区域,其中一个维度的部分区域会被裁剪
*/
_playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.playerView.layer addSublayer:_playerLayer];
//在整个界面中间的一个开始按钮,如果暂停时候就出现
UIButton *starButton = [[UIButton alloc]initWithFrame:CGRectMake((frame.size.width-50)/2, (frame.size.height-50)/2, 50, 50)];
self.starButton = starButton;
starButton.center = self.playerView.center;
[starButton setImage:[UIImage imageNamed:@"video-star"] forState:UIControlStateNormal];
starButton.hidden = YES;
[starButton addTarget:self action:@selector(clickStarButton:) forControlEvents:UIControlEventTouchUpInside];
[self.playerView addSubview:starButton];
//工具栏的view
UIView *bottomView = [[UIView alloc]initWithFrame:CGRectMake(0, frame.size.height-30, frame.size.width, 30)];
self.bottomView = bottomView;
bottomView.backgroundColor = RGBColor(0, 0, 0, 0.8);
bottomView.hidden = !_isShowToolbar;
[self.playerView addSubview:bottomView];
//工具栏上的 开始/暂停 按钮
UIButton *beginButton = [[UIButton alloc]initWithFrame:CGRectMake(10, (bottomView.frame.size.height-14)/2, 14, 14)];
self.beginButton = beginButton;
[beginButton setImage:[UIImage imageNamed:@"video-begin-1"] forState:UIControlStateNormal];
[beginButton setImage:[UIImage imageNamed:@"video-begin-2"] forState:UIControlStateSelected];
[beginButton addTarget:self action:@selector(beginAction:) forControlEvents:UIControlEventTouchUpInside];
beginButton.selected = YES;
[self.bottomView addSubview:beginButton];
//工具栏上的 音量/静音 按钮
UIButton *voiceButton = [[UIButton alloc]initWithFrame:CGRectMake(bottomView.frame.size.width-10-14, (bottomView.frame.size.height-14)/2, 14, 14)];
self.voiceButton = voiceButton;
[voiceButton setImage:[UIImage imageNamed:@"video-voice-1"] forState:UIControlStateNormal];
[voiceButton setImage:[UIImage imageNamed:@"video-voice-2"] forState:UIControlStateSelected];
[voiceButton addTarget:self action:@selector(voiceAction:) forControlEvents:UIControlEventTouchUpInside];
voiceButton.selected = YES;
self.player.volume = 0;
[self.bottomView addSubview:voiceButton];
//当前时间的Label
UILabel *beginLabel = [[UILabel alloc]initWithFrame:CGRectMake(CGRectGetMaxX(beginButton.frame)+10, beginButton.frame.origin.y, 30, 14)];
self.beginLabel = beginLabel;
beginLabel.font = [UIFont systemFontOfSize:14];
beginLabel.textColor = [UIColor whiteColor];
beginLabel.textAlignment = NSTextAlignmentCenter;
beginLabel.adjustsFontSizeToFitWidth = YES;
[self.bottomView addSubview:beginLabel];
//总共时长的Label
UILabel *endLabel = [[UILabel alloc]initWithFrame:CGRectMake(voiceButton.frame.origin.x-10-30, voiceButton.frame.origin.y, 30, 14)];
self.endLabel = endLabel;
endLabel.font = [UIFont systemFontOfSize:14];
endLabel.textColor = [UIColor whiteColor];
endLabel.textAlignment = NSTextAlignmentCenter;
endLabel.adjustsFontSizeToFitWidth = YES;
[self.bottomView addSubview:endLabel];
//进度条
UISlider *playSlider = [[UISlider alloc]initWithFrame:CGRectMake(CGRectGetMaxX(beginLabel.frame), (bottomView.frame.size.height-2)/2, bottomView.frame.size.width-2*(CGRectGetMaxX(beginLabel.frame)), 2)];
self.playSlider = playSlider;
playSlider.value = 0;
//左边的颜色
playSlider.minimumTrackTintColor = RGBColor(246, 89, 56, 1);
// playSlider.maximumTrackTintColor = [UIColor lightGrayColor];
// playSlider.thumbTintColor = [UIColor whiteColor];
//滑块的图片
[playSlider setThumbImage:[UIImage imageNamed:@"video-slider"] forState:UIControlStateNormal];
[self.bottomView addSubview:playSlider];
//滑条的滑动-触摸到滑块
[playSlider addTarget:self action:@selector(sliderTouchDown:) forControlEvents:UIControlEventTouchDown];
//滑条的滑动-抬起手指触摸结束
[playSlider addTarget:self action:@selector(sliderTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
//滑条的滑动-滑块的位置改变
[playSlider addTarget:self action:@selector(sliderTouchValueChanged:) forControlEvents:UIControlEventValueChanged];
//显示到父视图的上层
[self.playerView bringSubviewToFront:self.starButton];
[self.playerView bringSubviewToFront:self.bottomView];
}
return self;
}
3.进度条的拖动的相关方法
#pragma mark - 滑条的滑动事件 - 开始
-(void)sliderTouchDown:(UISlider *)slider{
//暂停
[self pause];
}
#pragma mark - 滑条的滑动事件 - 结束
-(void)sliderTouchUpInside:(UISlider *)slider{
_isSliding = NO;
//播放
[self play];
}
#pragma mark - 滑条的滑动事件 - 改变
-(void)sliderTouchValueChanged:(UISlider *)slider{
_isSliding = YES;
[self pause];
CMTime changedTime = CMTimeMakeWithSeconds(self.playSlider.value, 1.0);
[_playerItem seekToTime:changedTime completionHandler:^(BOOL finished) {
// 跳转完成后做某事
}];
}
4.工具栏上的按钮的一些方法
#pragma mark - 点击音量/静音按钮
-(void)voiceAction:(UIButton *)sender{
sender.selected = !sender.selected;
if (sender.selected) {
self.player.volume = 0;
}else{
self.player.volume = 1.0;
}
}
#pragma mark - 点击开始/暂停按钮
-(void)beginAction:(UIButton *)sender{
sender.selected = !sender.selected;
if (sender.selected) {
[self play];
}else{
[self pause];
}
}
#pragma mark - 点击中间的开始按钮
-(void)clickStarButton:(UIButton *)sender{
[self play];
self.beginButton.selected = YES;
}
5.对外的视频链接加载方法
#pragma mark - 加载视频地址
-(void)updatePlayerWithURL:(NSURL *)url{
//如果有视频源的切换的话,要记得先移除再添加
[self removeObserverAndNotificationWithPlayer];
_playerItem = [AVPlayerItem playerItemWithURL:url]; // create item
[_player replaceCurrentItemWithPlayerItem:_playerItem]; // replaceCurrentItem
[self addObserverAndNotification]; // 添加观察者,发布通知
}
6.添加的观察者和发布通知的方法以及对应的观察者和通知方法
#pragma mark - 添加观察者,发布通知
-(void)addObserverAndNotification{
[_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; // 观察status属性, 一共有三种属性
[self monitoringPlayback:_playerItem]; // 监听播放
// 播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
// 前台通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(enterForegroundNotification) name:UIApplicationWillEnterForegroundNotification object:nil];
// 后台通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(enterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
#pragma mark - 播放完成的通知
- (void)playbackFinished:(NSNotification *)notification {
_playerItem = [notification object];
// 是否无限循环
[_playerItem seekToTime:kCMTimeZero]; // 跳转到初始
[_player play]; // 是否无限循环
self.beginLabel.text = [self convertTime:0.0];
self.playSlider.value = 0;
}
#pragma mark - 前台通知
-(void)enterForegroundNotification{
[self play];
self.beginButton.selected = self.isPlaying;
}
#pragma mark - 后台通知
-(void)enterBackgroundNotification{
[self pause];
self.beginButton.selected = self.isPlaying;
_isShowToolbar = NO;
self.bottomView.hidden = !_isShowToolbar;
}
#pragma mark - Status的KVO
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
AVPlayerItem *item = (AVPlayerItem *)object;
if ([keyPath isEqualToString:@"status"]) {
// 判断status 的 状态
AVPlayerStatus status = [[change objectForKey:@"new"] intValue]; // 获取更改后的状态
if (status == AVPlayerStatusReadyToPlay) {
NSLog(@"准备播放");
// CMTime 本身是一个结构体
CMTime duration = item.duration; // 获取视频长度
NSLog(@"%.2f", CMTimeGetSeconds(duration));
// 设置视频时间
[self setMaxDuration:CMTimeGetSeconds(duration)];
// 播放
[self play];
} else if (status == AVPlayerStatusFailed) {
NSLog(@"AVPlayerStatusFailed");
} else {
NSLog(@"AVPlayerStatusUnknown");
}
}
}
#pragma mark - 添加监听播放
- (void)monitoringPlayback:(AVPlayerItem *)item {
__weak typeof(self)WeakSelf = self;
// 播放进度, 每秒执行30次, CMTime 为30分之一秒
_playTimeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 30.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
// 当前播放秒
float currentPlayTime = (double)item.currentTime.value/ item.currentTime.timescale;
// 更新slider, 如果正在滑动则不更新
if (_isSliding == NO) {
[WeakSelf updateVideoSlider:currentPlayTime];
}
}];
}
#pragma mark - 更新滑动条
- (void)updateVideoSlider:(float)currentTime {
self.playSlider.value = currentTime;
self.beginLabel.text = [self convertTime:currentTime];
}
#pragma mark - 设置最大时间
- (void)setMaxDuration:(CGFloat)duration {
self.playSlider.maximumValue = duration;
self.endLabel.text = [self convertTime:duration];
}
7.一个转换时间到字符串的工具方法
#pragma mark - 转换时间
-(NSString *)convertTime:(CGFloat)duration{
// 相对格林时间
NSDate *date = [NSDate dateWithTimeIntervalSince1970:duration];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
if (duration / 3600 >= 1) {
[formatter setDateFormat:@"HH:mm:ss"];
} else {
[formatter setDateFormat:@"mm:ss"];
}
NSString *showTimeNew = [formatter stringFromDate:date];
return showTimeNew;
}
8.播放和暂停方法,也可以对外开放
#pragma mark - 播放
- (void)play {
_isPlaying = YES;
[_player play]; // 调用avplayer 的play方法
self.starButton.hidden = YES;
}
#pragma mark - 暂停
- (void)pause {
_isPlaying = NO;
[_player pause];
self.starButton.hidden = NO;
}
9.添加触摸屏幕的一些操作,来控制工具栏的显示和隐藏
#pragma mark - 触屏-结束触摸
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
_isShowToolbar = !_isShowToolbar;
self.bottomView.hidden = !_isShowToolbar;
}
10.移除通知和观察者的方法
#pragma mark - 移除通知和观察者
-(void)removeObserverAndNotificationWithPlayer{
[_player replaceCurrentItemWithPlayerItem:nil];
[_playerItem removeObserver:self forKeyPath:@"status"];
[_player removeTimeObserver:_playTimeObserver];
_playTimeObserver = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - 销毁函数
-(void)dealloc{
[self removeObserverAndNotificationWithPlayer];
[_player removeTimeObserver:_playTimeObserver];
}
结语:基本上就这些就结束了,其实思路很简单,就是通过AVPlayer来播放视频,AVPlayer的显示要借助AVPlayerLayer,加载要借助AVPlayerItem。
把AVPlayerLayer加到主显示视图的layer上。
通过观察者来观察AVPlayerItem的status属性,当属性变为可以AVPlayerStatusReadyToPlay时就可以播放了。
在AVPlayerLayer里面有一个videoGravity属性,可以来控制显示的模式,是一个枚举类型,具体如下
AVLayerVideoGravityResize, // 非均匀模式。两个维度完全填充至整个视图区域
AVLayerVideoGravityResizeAspect, // 等比例填充,直到一个维度到达区域边界
AVLayerVideoGravityResizeAspectFill, // 等比例填充,直到填充满整个视图区域,其中一个维度的部分区域会被裁剪
然后再就是添加适当的通知方法,来进行判断并控制。
当程序进入后台时,播放器暂停。
当程序回到前台时,继续播放。
当视频播放完成事,是否要重新播放。
根据需要添加工具条和适当的方法,暂停,开始,静音,进度条等等。
先写这些吧。
测试所用视频链接:
http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4
网友评论