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

时间:2022-08-31 23:30:43



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


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


   

//
// MZPlayView.m
// 视频播放器常规功能
//
// Created by yubo on 16/7/27.
//

#import "MZPlayView.h"
#import <XXNibBridge/XXNibBridge.h>
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>


/**
* Description

关于视频这一块的业务操作全部封装在这个里面。 对外就提供一个NSUrl属性。

*/


typedef NS_ENUM(NSInteger,PanDirection){

PanDirectionHorizontalMoved, //横向移动
PanDirectionVerticalMoved //纵向移动

};


@interface MZPlayView()<XXNibBridge>


@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) bool playViewFlag; // 隐藏和显示标识
@property (nonatomic,strong) NSTimer *timer;
@property (nonatomic,assign) bool lockFlag; // 锁标识


@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 = 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<NSString *,id> *)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];// 计算缓冲进度
CMTime duration = _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

视图只设置了   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 ---- 视频播放器常规功能



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






相关文章