AVPlayer ---- 视频播放器常规功能





大部分代码来源于一个Demo中的一个功能点, Demo已经给不知道地址了, 囧。


主要代码如下:播放器组件类



// //MZPlayView.m //视频播放器常规功能 // //Created by yubo on 16/7/27. //#import "MZPlayView.h" #import #import #import /** *Description关于视频这一块的业务操作全部封装在这个里面。 对外就提供一个NSUrl属性。 */typedef NS_ENUM(NSInteger,PanDirection){PanDirectionHorizontalMoved, //横向移动 PanDirectionVerticalMoved//纵向移动}; @interface MZPlayView()@property (nonatomic,strong) AVPlayer *player; @property (nonatomic,strong) AVPlayerItem *playerItem; @property (nonatomic,strong) AVPlayerLayer *playerLayer; @property (weak, nonatomic) IBOutlet UIButton *backBtn; @property (weak, nonatomic) IBOutlet UILabel *forwardLabel; @property (weak, nonatomic) IBOutlet UILabel *currentTimeLabel; @property (weak, nonatomic) IBOutlet UIProgressView *progressView; @property (weak, nonatomic) IBOutlet UILabel *totalTimeLabel; @property (weak, nonatomic) IBOutlet UIButton *fullScreanBtn; @property (weak, nonatomic) IBOutlet UISlider *sliderView; @property (weak, nonatomic) IBOutlet UIButton *startBtn; @property (weak, nonatomic) IBOutlet UIButton *lockBtn; @property (nonatomic,assign) boolplayViewFlag; // 隐藏和显示标识 @property (nonatomic,strong) NSTimer *timer; @property (nonatomic,assign) boollockFlag; // 锁标识@property (nonatomic,assign) PanDirection panDirection; @property (nonatomic, assign) BOOL isVolume; // 是否调节音量 @property (nonatomic, strong) UISlider *volumeViewSlider; @property (nonatomic, assign) CGFloat sumTime; // 用来保存的总时长@end@implementation MZPlayView- (void)awakeFromNib{ [super awakeFromNib]; [_sliderView setThumbImage:[UIImage imageNamed:@"slider"] forState:UIControlStateNormal]; [self insertSubview:_progressView belowSubview:_sliderView]; // 滑块左边的颜色 _sliderView.minimumTrackTintColor = [UIColor orangeColor]; // 滑块右边的颜色 _sliderView.maximumTrackTintColor = [UIColor clearColor]; }- (void)setUrl:(NSURL *)url{ _playerItem = [AVPlayerItem playerItemWithURL:url]; _player = [AVPlayer playerWithPlayerItem:_playerItem]; _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; [self.layer insertSublayer:_playerLayer atIndex:0]; [_player play]; _forwardLabel.hidden = YES; // 添加播完通知 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem]; // 添加手势 [self createGesture]; //获取系统音量 [self configureVolume]; // 添加事件 [self sliderAddEvent]; // 通过定时器获取 时间, _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(playerTimerAction) userInfo:nil repeats:YES]; // 计算缓冲 [_playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; [_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; }#pragma mark滑动事件添加 - (void)sliderAddEvent{// slider开始滑动事件 [_sliderView addTarget:self action:@selector(progressSliderTouchBegan:) forControlEvents:UIControlEventTouchDown]; // slider滑动中事件 [_sliderView addTarget:self action:@selector(progressSliderValueChanged:) forControlEvents:UIControlEventValueChanged]; // slider结束滑动事件 [_sliderView addTarget:self action:@selector(progressSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside]; }// 开始滑动- (void)progressSliderTouchBegan:(UISlider *)slider{ // 取消延迟 //[NSObject cancelPreviousPerformRequestsWithTarget:self]; }// 开始变化- (void)progressSliderValueChanged:(UISlider *)slider{// 拖动改变视频播放进度 if (_player.status == AVPlayerItemStatusReadyToPlay) { // 总秒数 CGFloat total = (CGFloat)_playerItem.duration.value / _playerItem.duration.timescale; // 已经拖动到了现在的秒数了。 NSInteger dragedSeconds = floorf(total * slider.value); //转换成CMTime才能给player来控制播放进度---这个类用来控制时间的。 CMTime dragedCMTime = CMTimeMake(dragedSeconds, 1); [_player pause]; [_player seekToTime:dragedCMTime completionHandler:^(BOOL finished) {[_player play]; }]; }}#pragma mark slider事件结束- (void)progressSliderTouchEnded:(UISlider *)slider{}#pragma mark 重新布局 frame- (void)layoutSubviews{ _playerLayer.frame = self.bounds; }; #pragma mark 添加手势, 点击隐藏视频所有按钮- (void)createGesture{ UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(playViewHanlder:)]; tap.numberOfTapsRequired = 1; [self addGestureRecognizer:tap]; }#pragma mark 手势回调处理- (void)playViewHanlder:(UITapGestureRecognizer *)recognizer{ if (!_lockFlag) { if (recognizer.state == UIGestureRecognizerStateRecognized) { if (!_playViewFlag) { [self hiddlePlayView]; }else{ [self showPlayView]; } } } }#pragma mark 隐藏组件- (void)hiddlePlayView{ [UIView animateWithDuration:0.5 animations:^{ _backBtn.alpha = 0; _currentTimeLabel.alpha = 0; _progressView.alpha = 0; _totalTimeLabel.alpha = 0; _fullScreanBtn.alpha = 0; _sliderView.alpha = 0; _startBtn.alpha = 0; } completion:^(BOOL finished) { _playViewFlag = !_playViewFlag; }]; }#pragma mark 显示组件- (void)showPlayView{ [UIView animateWithDuration:0.5 animations:^{ _backBtn.alpha = 1; _currentTimeLabel.alpha = 1; _progressView.alpha = 1; _totalTimeLabel.alpha = 1; _fullScreanBtn.alpha = 1; _sliderView.alpha = 1; _startBtn.alpha = 1; } completion:^(BOOL finished) { _playViewFlag = !_playViewFlag; }]; }#pragma mark播放完后---通知处理- (void)moviePlayDidEnd:(NSNotification *)notification{}#pragma mark 播放或者暂停- (IBAction)startAction:(id)sender { UIButton *btn = (UIButton *)sender; if (btn.selected) { [_player play]; }else{ [_player pause]; } btn.selected = !btn.selected; }#pragma mark 全屏按钮事件- (IBAction)fullScreanAction:(id)sender {UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation; switch (interfaceOrientation) { case UIInterfaceOrientationPortraitUpsideDown: // 电池栏在下[self interfaceOrientation:UIInterfaceOrientationLandscapeRight]; break; case UIInterfaceOrientationPortrait: // 电池栏在上[self interfaceOrientation:UIInterfaceOrientationLandscapeRight]; break; case UIInterfaceOrientationLandscapeLeft: // 电池栏在右边[self interfaceOrientation:UIInterfaceOrientationPortrait]; break; case UIInterfaceOrientationLandscapeRight: // 电池栏左边[self interfaceOrientation:UIInterfaceOrientationPortrait]; break; default: break; } }#pragma mark强制转屏相关- (void)interfaceOrientation:(UIInterfaceOrientation)orientation{ // arc下 if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) { SEL selector = NSSelectorFromString(@"setOrientation:"); NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]]; [invocation setSelector:selector]; [invocation setTarget:[UIDevice currentDevice]]; int val = orientation; [invocation setArgument:&val atIndex:2]; [invocation invoke]; } }#pragma mark 通过定时器获取视频的时间问题- (void)playerTimerAction{ if (_playerItem.duration.timescale != 0) {_sliderView.maximumValue = https://www.it610.com/article/1; //音乐总共时长 _sliderView.value = CMTimeGetSeconds([_playerItem currentTime]) / (_playerItem.duration.value / _playerItem.duration.timescale); //当前进度//duration 总时长 NSInteger durMin = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale; //总秒 NSInteger durSec = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale / 60; //总分钟 _totalTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld",durSec,durMin]; //当前时长进度progress NSInteger proMin = (NSInteger)CMTimeGetSeconds([_playerItem currentTime]); //当前秒 NSInteger proSec = (NSInteger)CMTimeGetSeconds([_playerItem currentTime]) / 60; //当前分 _currentTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", proSec, proMin]; }}#pragma mark 计算出总时间 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{if (object == _playerItem) {if ([keyPath isEqualToString:@"status"]) { // 这里代表添加完成 if (_playerItem.status == AVPlayerItemStatusReadyToPlay) { // 添加滑动手势 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panDirection:)]; [self addGestureRecognizer:pan]; } }else if([keyPath isEqualToString:@"loadedTimeRanges"]){ NSTimeInterval timeInterval = [self availableDuration]; // 计算缓冲进度 CMTimeduration = _playerItem.duration; CGFloat totalDuration = CMTimeGetSeconds(duration); [_progressView setProgress:timeInterval / totalDuration animated:NO]; } } }#pragma mark计算缓存- (NSTimeInterval)availableDuration { NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges]; CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue]; // 获取缓冲区域 float startSeconds = CMTimeGetSeconds(timeRange.start); float durationSeconds = CMTimeGetSeconds(timeRange.duration); NSTimeInterval result = startSeconds + durationSeconds; // 计算缓冲总进度 return result; }#pragma mark 锁屏- (IBAction)lockAction:(id)sender {UIButton *btn = (UIButton *)sender; btn.selected = !btn.selected; _lockFlag = btn.selected; if (btn.selected) { [btn setImage:[UIImage imageNamed:@"lock-nor"] forState:UIControlStateNormal]; }else{ [btn setImage:[UIImage imageNamed:@"unlock-nor"] forState:UIControlStateNormal]; } }#pragma mark平移手势- (void)panDirection:(UIPanGestureRecognizer *)pan{// 根据在Uiview的位置,调节音量或者亮度 CGPoint locationPoint = [pan locationInView:self]; NSLog(@"当前点的位置--->%@",NSStringFromCGPoint(locationPoint)); // 拖动的速度---->返回值就是你拖动时X和Y轴上的速度,速度是矢量,有方向 CGPoint veloctyPoint = [pan velocityInView:self]; NSLog(@"拖动的速度--->%@",NSStringFromCGPoint(veloctyPoint)); // 判断是垂直移动还是水平移动 switch (pan.state) { case UIGestureRecognizerStateBegan:{ // 开始移动CGFloat x = fabs(veloctyPoint.x); CGFloat y = fabs(veloctyPoint.y); if (x > y) { // 水平移动 _panDirection = PanDirectionHorizontalMoved; CMTime time = _player.currentTime; _sumTime = time.value/time.timescale; NSLog(@"===%f",_sumTime); }else if(x < y){ // 垂直移动 _panDirection = PanDirectionVerticalMoved; if (locationPoint.x > self.bounds.size.width/2) { // 右边是控制音量 _isVolume = true; }else{ // 左边是控制亮度 _isVolume = false; } } } break; case UIGestureRecognizerStateChanged:{ // 正在移动if (self.panDirection == PanDirectionHorizontalMoved) { // 水平[self horizontalMoved:veloctyPoint.x]; // 水平移动的方法只要x方向的值}else{ // 垂直[self verticalMoved:veloctyPoint.y]; // 垂直移动方法只要y方向的值} } break; case UIGestureRecognizerStateEnded:{NSLog(@"移动停止l......"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 隐藏视图 _forwardLabel.hidden = YES; }); if (self.panDirection == PanDirectionHorizontalMoved) { //转换成CMTime才能给player来控制播放进度 CMTime dragedCMTime = CMTimeMake(self.sumTime, 1); [_player pause]; [_player seekToTime:dragedCMTime completionHandler:^(BOOL finish){[_player play]; }]; // 把sumTime滞空,不然会越加越多 self.sumTime = 0; }else{}} break; default: break; } }#pragma mark垂直移动处理。- (void)verticalMoved:(CGFloat)value{if (_isVolume) { // 控制音量 // 更改系统的音量 self.volumeViewSlider.value -= value / 10000; // 越小幅度越小// 这个就对手机有效果, 最大的值为 1。 NSLog(@"声音----->%lf", self.volumeViewSlider.value); }else{ // 调节亮度[UIScreen mainScreen].brightness -= value / 10000; NSString *brightness = [NSString stringWithFormat:@"亮度%.0f%%",[UIScreen mainScreen].brightness/1.0*100]; _forwardLabel.hidden = NO; _forwardLabel.text = brightness; } }//获取系统音量 - (void)configureVolume { // 系统声音控制 MPVolumeView *volumeView = [[MPVolumeView alloc] init]; _volumeViewSlider = nil; for (UIView *view in [volumeView subviews]){ if ([view.class.description isEqualToString:@"MPVolumeSlider"]){ _volumeViewSlider = (UISlider *)view; break; } }// 使用这个category的应用不会随着手机静音键打开而静音,可在手机静音下播放声音 NSError *setCategoryError = nil; BOOL success = [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &setCategoryError]; if (!success) { /* handle the error in setCategoryError */ } }#pragma mark - pan水平移动的方法- (void)horizontalMoved:(CGFloat)value{ // 快进快退的方法 NSString *style = @""; if (value < 0) { style = @"<<"; } else if (value > 0){ style = @">>"; }// 每次滑动需要叠加时间 self.sumTime += value / 200; // 需要限定sumTime的范围 CMTime totalTime = self.playerItem.duration; CGFloat totalMovieDuration = (CGFloat)totalTime.value/totalTime.timescale; if (self.sumTime > totalMovieDuration) { self.sumTime = totalMovieDuration; }else if (self.sumTime < 0){ self.sumTime = 0; }// 当前快进的时间 NSString *nowTime = [self durationStringWithTime:(int)self.sumTime]; // 总时间 NSString *durationTime = [self durationStringWithTime:(int)totalMovieDuration]; // 给label赋值 _forwardLabel.text = [NSString stringWithFormat:@"%@ %@ / %@",style, nowTime, durationTime]; _forwardLabel.hidden = NO; }#pragma mark - 根据时长求出字符串- (NSString *)durationStringWithTime:(int)time { // 获取分钟 NSString *min = [NSString stringWithFormat:@"%02d",time / 60]; // 获取秒数 NSString *sec = [NSString stringWithFormat:@"%02d",time % 60]; return [NSString stringWithFormat:@"%@:%@", min, sec]; }@end


【AVPlayer ---- 视频播放器常规功能】
视图只设置了6+ 的适配, 其他的没做处理。
AVPlayer ---- 视频播放器常规功能
文章图片






调用
self.view.backgroundColor = [UIColor blackColor];

NSURL *url = [NSURL URLWithString:@"http://baobab.wdjcdn.com/14562919706254.mp4"]; //NSURL *url = [[NSBundle mainBundle] URLForResource:@"minion_01" withExtension:@"mp4"]; [_playView setUrl:url];






效果:


AVPlayer ---- 视频播放器常规功能
文章图片






实现了,暂停, 拖放,声音,亮度, 锁屏。 这一些基本简单操作。










    推荐阅读