基于AVPlayer的播放器

//
// PlayerMenu.h
// video
//
// Created by admin on 17/5/5.
// Copyright © 2017年 wsl. All rights reserved.
//

#import <UIKit/UIKit.h>

@class PlayerSlider;
@protocol PlayerMenuDelegate <NSObject>

/**返回按钮代理*/
- (void)backButtonAction:(UIButton *)sender;
/**播放按钮代理*/
- (void)playButtonAction:(UIButton *)sender;
/**全屏按钮代理*/
- (void)fullButtonAction:(UIButton *)sender;
/**开始滑动*/
- (void)progressSliderTouchBegan:(PlayerSlider *)sender;
/**滑动中*/
- (void)progressSliderValueChanged:(PlayerSlider *)sender;
/**滑动结束*/
- (void)progressSliderTouchEnded:(PlayerSlider *)sender;
/**失败按钮代理*/
- (void)failButtonAction:(UIButton *)sender;
/**重播按钮代理*/
- (void)repeatButtonAction:(UIButton*)sender;

@end

@interface PlayerMenuView : UIButton


@property (nonatomic, strong) UIView *topToolBar;               //顶部工具条
@property (nonatomic, strong) UIView *bottomToolBar;            //底部工具条
@property (nonatomic, strong) UIActivityIndicatorView *activity;//菊花转
@property (nonatomic, strong) UIButton *backButton;             //返回按钮
@property (nonatomic, strong) UIButton *playButton;             //播放按钮
@property (nonatomic, strong) UIButton *fullButton;             //全屏按钮
@property (nonatomic, strong) UILabel *currentTimeLabel;        //当前时间
@property (nonatomic, strong) UILabel *totalTimeLabel;          //总时间
@property (nonatomic, strong) UIProgressView *progress;         //缓冲进度
@property (nonatomic, strong) PlayerSlider *slider;             //进度条
@property (nonatomic, strong) UIButton *failButton;             //失败按钮
@property (nonatomic, strong) UIButton *repeatButton;           //重复按钮

@property (nonatomic, weak)   id<PlayerMenuDelegate> delegate;

@end



@interface PlayerSlider : UISlider

@end

.

//
//  PlayerMenu.m
//  video
//
//  Created by admin on 17/5/5.
//  Copyright © 2017年 wsl. All rights reserved.
//

#import "PlayerMenuView.h"
#import "Masonry.h"

#define ProgressColor     [UIColor colorWithRed:0.54118 green:0.51373 blue:0.50980 alpha:1.00000] //进度条颜色
#define ProgressTintColor [UIColor colorWithRed:248/255.0 green:248/255.0 blue:248/255.0 alpha:1.00000]//缓冲颜色
#define PlayFinishColor   [UIColor colorWithRed:62/255.0 green:188/255.0 blue:181/255.0 alpha:1.00000]//播放完成颜色
#define Padding           10    //间距
#define ToolBarHeight     40    //顶部底部工具条高度

@interface PlayerMenuView()

@property (nonatomic, strong) UIButton *bgBtn;

@end

@implementation PlayerMenuView

-(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        [self setupUI];
    }
    return self;
}

-(void)setupUI{
    [self addSubview:self.bottomToolBar];
    [self addSubview:self.repeatButton];
    [self addSubview:self.topToolBar];
    [self addSubview:self.activity];
    [self.topToolBar addSubview:self.backButton];
    [self.topToolBar addSubview:self.bgBtn];
    [self.bottomToolBar addSubview:self.playButton];
    [self.bottomToolBar addSubview:self.fullButton];
    [self.bottomToolBar addSubview:self.currentTimeLabel];
    [self.bottomToolBar addSubview:self.totalTimeLabel];
    [self.bottomToolBar addSubview:self.progress];
    [self.bottomToolBar addSubview:self.slider];
    [self addSubview:self.failButton];
    [self makeConstraints];
    
    
    self.topToolBar.backgroundColor = [UIColor colorWithRed:0.00000f green:0.00000f blue:0.00000f alpha:0.01000f];
    self.bottomToolBar.backgroundColor = [UIColor colorWithRed:0.00000f green:0.00000f blue:0.00000f alpha:0.50000f];
}
#pragma mark - 约束
- (void)makeConstraints{
    //底部工具条
    [self.bottomToolBar mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.bottom.equalTo(self);
        make.height.mas_equalTo(ToolBarHeight);
    }];
    [self.repeatButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.right.bottom.equalTo(self);
    }];
    //顶部工具条
    [self.topToolBar mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.top.equalTo(self);
        make.height.mas_equalTo(ToolBarHeight);
    }];
    //转子
    [self.activity mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self);
    }];
    //返回按钮
    [self.backButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.mas_equalTo(Padding);
        make.bottom.mas_equalTo(-Padding);
        make.width.equalTo(self.backButton.mas_height);
    }];
    [self.bgBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.bottom.equalTo(self.topToolBar);
        make.width.mas_equalTo(80);
    }];
    //播放按钮
    [self.playButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.mas_equalTo(Padding);
        make.bottom.mas_equalTo(-Padding);
        make.width.equalTo(self.backButton.mas_height);
    }];
    //全屏按钮
    [self.fullButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.bottom.mas_equalTo(-Padding);
        make.top.mas_equalTo(Padding);
        make.width.equalTo(self.backButton.mas_height);
    }];
    //当前播放时间
    [self.currentTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.playButton.mas_right).offset(Padding);
        make.centerY.equalTo(self.bottomToolBar);
    }];
    //总时间
    [self.totalTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.fullButton.mas_left).offset(-Padding);
        make.centerY.equalTo(self.bottomToolBar);
    }];
    //缓冲条
    [self.progress mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.currentTimeLabel.mas_right).offset(Padding);
        make.right.equalTo(self.totalTimeLabel.mas_left).offset(-Padding);
        make.height.mas_equalTo(2);
        make.centerY.equalTo(self.bottomToolBar);
    }];
    //滑杆
    [self.slider mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.progress);
    }];
    //失败按钮
    [self.failButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self);
    }];
}

#pragma mark - 按钮点击事件
//返回按钮
- (void)backButtonAction:(UIButton *)sender{
    if ([self.delegate respondsToSelector:@selector(backButtonAction:)]) {
        [self.delegate backButtonAction:sender];
    }
}
//播放按钮
- (void)playButtonAction:(UIButton *)sender{
    sender.selected = !sender.selected;
    if ([self.delegate respondsToSelector:@selector(playButtonAction:)]) {
        [self.delegate playButtonAction:sender];
    }
}
//全屏按钮
- (void)fullButtonAction:(UIButton *)sender{
    sender.selected = !sender.selected;
    self.topToolBar.hidden = sender.selected;
    if ([self.delegate respondsToSelector:@selector(fullButtonAction:)]) {
        [self.delegate fullButtonAction:sender];
    }
}
//失败按钮
- (void)failButtonAction:(UIButton *)sender{
    self.failButton.hidden = YES;
    [self.activity startAnimating];
    
    if ([self.delegate respondsToSelector:@selector(failButtonAction:)]) {
        [self.delegate failButtonAction:sender];
    }
}
//重播按钮
- (void)repeatButtonAction:(UIButton*)sender{
    self.repeatButton.hidden = YES;
    [self.activity startAnimating];
    if ([self.delegate respondsToSelector:@selector(repeatButtonAction:)]) {
        [self.delegate repeatButtonAction:sender];
    }
}

#pragma mark - 滑杆
//开始滑动
- (void)progressSliderTouchBegan:(PlayerSlider *)sender{
    if ([self.delegate respondsToSelector:@selector(progressSliderTouchBegan:)]) {
        [self.delegate progressSliderTouchBegan:sender];
    }
}
//滑动中
- (void)progressSliderValueChanged:(PlayerSlider *)sender{
    if ([self.delegate respondsToSelector:@selector(progressSliderValueChanged:)]) {
        [self.delegate progressSliderValueChanged:sender];
    }
}
//滑动结束
- (void)progressSliderTouchEnded:(PlayerSlider *)sender{
    if ([self.delegate respondsToSelector:@selector(progressSliderTouchEnded:)]) {
        [self.delegate progressSliderTouchEnded:sender];
    }
}

#pragma mark - 懒加载
//顶部工具条
- (UIView *) topToolBar{
    if (nil == _topToolBar){
        _topToolBar = [[UIView alloc] init];
        _topToolBar.userInteractionEnabled = YES;
    }
    return _topToolBar;
}
//底部工具条
- (UIView *) bottomToolBar{
    if (nil == _bottomToolBar){
        _bottomToolBar = [[UIView alloc] init];
        _bottomToolBar.userInteractionEnabled = YES;
    }
    return _bottomToolBar;
}
//转子
- (UIActivityIndicatorView *)activity{
    if (nil == _activity){
        _activity = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
        [_activity startAnimating];
    }
    return _activity;
}
//返回按钮
- (UIButton *) backButton{
    if (nil == _backButton){
        _backButton = [[UIButton alloc] init];
        [_backButton setImage:[UIImage imageNamed:@"VideoBackBtn"] forState:UIControlStateNormal];
        [_backButton setImage:[UIImage imageNamed:@"VideoBackBtn"] forState:UIControlStateHighlighted];
        [_backButton addTarget:self action:@selector(backButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _backButton;
}
//返回按钮遮罩
-(UIButton *)bgBtn{
    if (nil == _bgBtn) {
        _bgBtn = [[UIButton alloc]init];
        [_bgBtn addTarget:self action:@selector(backButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _bgBtn;
}
//播放按钮
- (UIButton *) playButton{
    if (nil == _playButton){
        _playButton = [[UIButton alloc] init];
        [_playButton setImage:[UIImage imageNamed:@"VideoPlayBtn"] forState:UIControlStateNormal];
        [_playButton setImage:[UIImage imageNamed:@"VideoPauseBtn"] forState:UIControlStateSelected];
        [_playButton addTarget:self action:@selector(playButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _playButton;
}
//全屏按钮
- (UIButton *) fullButton{
    if (nil == _fullButton){
        _fullButton = [[UIButton alloc] init];
        [_fullButton setImage:[UIImage imageNamed:@"VideoMaxBtn"] forState:UIControlStateNormal];
        [_fullButton setImage:[UIImage imageNamed:@"VideoMinBtn"] forState:UIControlStateSelected];
        [_fullButton addTarget:self action:@selector(fullButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _fullButton;
}
//当前播放时间
- (UILabel *) currentTimeLabel{
    if (nil == _currentTimeLabel){
        _currentTimeLabel = [[UILabel alloc] init];
        _currentTimeLabel.textColor = [UIColor whiteColor];
        _currentTimeLabel.font = [UIFont systemFontOfSize:kRealValue(12)];
        _currentTimeLabel.text = @"00:00";
        _currentTimeLabel.textAlignment = NSTextAlignmentCenter;
    }
    return _currentTimeLabel;
}
//总时间
- (UILabel *) totalTimeLabel{
    if (nil == _totalTimeLabel){
        _totalTimeLabel = [[UILabel alloc] init];
        _totalTimeLabel.textColor = [UIColor whiteColor];
        _totalTimeLabel.font = [UIFont systemFontOfSize:kRealValue(12)];
        _totalTimeLabel.text = @"00:00";
        _totalTimeLabel.textAlignment = NSTextAlignmentCenter;
    }
    return _totalTimeLabel;
}
//缓冲条
- (UIProgressView *) progress{
    if (nil == _progress){
        _progress = [[UIProgressView alloc] init];
        _progress.trackTintColor = ProgressColor;
        _progress.progressTintColor = ProgressTintColor;
    }
    return _progress;
}
//滑动条
- (PlayerSlider *) slider{
    if (nil == _slider){
        _slider = [[PlayerSlider alloc] init];
        // slider开始滑动事件
        [_slider addTarget:self action:@selector(progressSliderTouchBegan:) forControlEvents:UIControlEventTouchDown];
        // slider滑动中事件
        [_slider addTarget:self action:@selector(progressSliderValueChanged:) forControlEvents:UIControlEventValueChanged];
        // slider结束滑动事件
        [_slider addTarget:self action:@selector(progressSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchUpOutside];
        //左边颜色
        _slider.minimumTrackTintColor = PlayFinishColor;
        //右边颜色
        _slider.maximumTrackTintColor = [UIColor clearColor];
    }
    return _slider;
}
//加载失败按钮
- (UIButton *) failButton{
    if (nil == _failButton) {
        _failButton = [[UIButton alloc] init];
        _failButton.hidden = YES;
        [_failButton setTitle:@"加载失败,点击重试" forState:UIControlStateNormal];
        [_failButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        _failButton.titleLabel.font = [UIFont systemFontOfSize:kRealValue(14.0f)];
        _failButton.backgroundColor = [UIColor colorWithRed:0.00000f green:0.00000f blue:0.00000f alpha:0.50000f];
        [_failButton addTarget:self action:@selector(failButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _failButton;
}

-(UIButton *)repeatButton{
    if (nil == _repeatButton) {
        _repeatButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _repeatButton.hidden = YES;
        [_repeatButton setTitle:@"重播" forState:UIControlStateNormal];
        [_repeatButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        _repeatButton.titleLabel.font = [UIFont systemFontOfSize:kRealValue(14.0f)];
        _repeatButton.backgroundColor = [UIColor blackColor];
        [_repeatButton addTarget:self action:@selector(repeatButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _repeatButton;
}

@end




@implementation PlayerSlider

-(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        [self setup];
    }
    return self;
}

- (void)setup {
    UIImage *thumbImage = [UIImage imageNamed:@"VideoRound"];
    [self setThumbImage:thumbImage forState:UIControlStateHighlighted];
    [self setThumbImage:thumbImage forState:UIControlStateNormal];
}


// 控制slider的宽和高,这个方法才是真正的改变slider滑道的高的
- (CGRect)trackRectForBounds:(CGRect)bounds{
    [super trackRectForBounds:bounds];
    return CGRectMake(bounds.origin.x, bounds.origin.y, CGRectGetWidth(bounds), 2);
}
//修改滑块位置
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value {
    rect.origin.x = rect.origin.x - 6 ;
    rect.size.width = rect.size.width + 12;
    return CGRectInset ([super thumbRectForBounds:bounds trackRect:rect value:value], 10 , 10);
}


@end

.

//
//  PlayerView.h
//  video
//
//  Created by admin on 17/5/5.
//  Copyright © 2017年 wsl. All rights reserved.
//

#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger,PlayerViewFillMode){
    Resize = 0,          //拉伸占满整个播放器,不按原比例拉伸
    ResizeAspect,        //按原视频比例显示,是竖屏的就显示出竖屏的,两边留黑
    ResizeAspectFill,    //按照原比例拉伸占满整个播放器,但视频内容超出部分会被剪切
};


@protocol PlayerViewDelegate <NSObject>

@optional
//返回按钮
- (void)clickMenuBackButton;
//播放结束
- (void)endPlay;
//旋转屏幕方向
- (void)autoFullScreen:(UIInterfaceOrientation)interfaceOrientation;

@end

@interface PlayerView : UIView

@property (nonatomic,strong) NSURL *url;                    //视频url
@property (nonatomic,assign) BOOL autoFullScreen;           //旋转自动全屏,默认Yes
@property (nonatomic,assign) BOOL repeatPlay;               //重复播放,默认No
@property (nonatomic,assign) BOOL isLandscape;              //是否支持横屏,默认No
@property (nonatomic,assign) PlayerViewFillMode fillMode;   //拉伸方式,默认全屏填充
@property (weak, nonatomic)  id<PlayerViewDelegate> delegate;
@property (assign,nonatomic) BOOL isShowTopTool;

/**播放*/
- (void)playVideo;
/**暂停*/
- (void)pausePlay;

/**销毁播放器*/
- (void)destroyPlayer;

@end

.

//
//  PlayerView.m
//  video
//
//  Created by admin on 17/5/5.
//  Copyright © 2017年 wsl. All rights reserved.
//

#import "PlayerView.h"
#import <AVFoundation/AVFoundation.h>
#import "PlayerMenuView.h"

// 播放器的几种状态
typedef NS_ENUM(NSInteger, CLPlayerState) {
    CLPlayerStateFailed,     // 播放失败
    CLPlayerStateBuffering,  // 缓冲中
    CLPlayerStatePlaying,    // 播放中
    CLPlayerStateStopped,    // 停止播放
    CLPlayerStatePause       // 暂停播放
};
//方向枚举
typedef NS_ENUM(NSInteger,Direction){
    Letf = 0,
    Right,
};

#define DisappearTime  10     //工具条消失时间
#define KScreen_Width  [UIScreen mainScreen].bounds.size.width
#define KScreen_Height  [UIScreen mainScreen].bounds.size.height
#define KScreen_Bounds  [UIScreen mainScreen].bounds


@interface PlayerView()<PlayerMenuDelegate>

@property (nonatomic, strong) AVPlayer *player;          //播放器
@property (nonatomic, strong) AVPlayerLayer *playerLayer;//playerLayer
@property (nonatomic, strong) AVPlayerItem *playerItem;  //播放器item
@property (nonatomic, assign) CLPlayerState state;       //播放器的几种状态
@property (nonatomic, assign) CGRect customFarme;        //控件原始Farme
@property (nonatomic, strong) UIView *fatherView;        //父控件
@property (nonatomic, copy)   NSString *videoFillMode;   //视频拉伸模式
@property (nonatomic, strong) NSTimer *timer;            //定时器
@property (nonatomic, strong) NSTimer *sliderTimer;      //刷新进度的定时器
@property (nonatomic, strong) PlayerMenuView *playerMenuView;//工具条

@property (nonatomic, assign) BOOL isFullScreen;         //全屏标记
@property (nonatomic, assign) BOOL landscape;            //横屏标记
@property (nonatomic, assign) BOOL isDisappear;          //工具条隐藏标记
@property (nonatomic, assign) BOOL isUserPlay;           //用户点击播放标记


@end

@implementation PlayerView

-(PlayerMenuView *)playerMenuView{
    if (nil == _playerMenuView) {
        _playerMenuView = [[PlayerMenuView alloc]init];
        _playerMenuView.delegate = self;
        
        //点击事件
        [_playerMenuView addTarget:self action:@selector(disappearAction:) forControlEvents:UIControlEventTouchUpInside];
        
        //计时器,循环执行
        _sliderTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timeStack) userInfo:nil repeats:YES];
        
        //定时器,工具条消失
        _timer = [NSTimer scheduledTimerWithTimeInterval:DisappearTime target:self selector:@selector(disappear) userInfo:nil repeats:NO];
    }
    return _playerMenuView;
}

-(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor blackColor];
        //
        _isFullScreen   = NO;
        _autoFullScreen = YES;
        _repeatPlay     = NO;
        _isLandscape    = NO;
        _landscape      = NO;
        _isDisappear    = NO;
        _isUserPlay     = NO;
        
        //
        //开启
        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
        //注册屏幕旋转通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientChange:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];
        //APP运行状态通知,将要被挂起
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil];
        // app进入前台
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayground:) name:UIApplicationDidBecomeActiveNotification object:nil];
        
        
        //最上面的View, 菜单
        [self addSubview:self.playerMenuView];
    }
    return self;
}
-(void)layoutSubviews{
    [super layoutSubviews];
    self.playerLayer.frame = self.bounds;
    self.playerMenuView.frame = self.bounds;
}

#pragma mark - 屏幕旋转通知
- (void)orientChange:(NSNotification *)notification{

}
#pragma mark - APP活动通知
- (void)appDidEnterBackground:(NSNotification *)note{
    //将要挂起,停止播放
    [self pausePlay];
}
- (void)appDidEnterPlayground:(NSNotification *)note{
    //继续播放
    if (_isUserPlay) {
        [self playVideo];
    }
}


-(void)setIsShowTopTool:(BOOL)isShowTopTool{
    _isShowTopTool = isShowTopTool;
    self.playerMenuView.topToolBar.hidden = isShowTopTool;
}

#pragma mark - 定时器
- (void)timeStack{
    //判断总时长
    if (_playerItem.duration.timescale != 0){
        //总共时长
        self.playerMenuView.slider.maximumValue = 1;
        
        //当前进度
        self.playerMenuView.slider.value = CMTimeGetSeconds([_playerItem currentTime]) / (_playerItem.duration.value / _playerItem.duration.timescale);
        
        //当前时长进度progress
        NSInteger proMin = (NSInteger)CMTimeGetSeconds([_player currentTime]) / 60;//当前秒
        NSInteger proSec = (NSInteger)CMTimeGetSeconds([_player currentTime]) % 60;//当前分钟
        self.playerMenuView.currentTimeLabel.text = [NSString stringWithFormat:@"%02zd:%02zd", proMin, proSec];
        
        //duration 总时长
        NSInteger durMin = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale / 60;//总分钟
        NSInteger durSec = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale % 60;//总秒
        self.playerMenuView.totalTimeLabel.text = [NSString stringWithFormat:@"%02zd:%02zd", durMin, durSec];
    }
}
//定时消失
- (void)disappear{
    [UIView animateWithDuration:0.5 animations:^{
        //self.playerMenuView.topToolBar.alpha    = 0;
        self.playerMenuView.bottomToolBar.alpha = 0;
    }];
}

#pragma mark - 取消定时器
//销毁所有定时器
- (void)destroyAllTimer{
    [_sliderTimer invalidate];
    [_timer invalidate];
    _sliderTimer = nil;
    _timer = nil;
}
//销毁定时消失定时器
- (void)destroyTimer{
    [_timer invalidate];
    _timer = nil;
}



#pragma mark - set
//MARK: - 视频拉伸方式
-(void)setFillMode:(PlayerViewFillMode)fillMode{
    switch (fillMode){
        case ResizeAspectFill:
            //原比例拉伸视频,直到两边屏幕都占满,但视频内容有部分会被剪切
            _videoFillMode = AVLayerVideoGravityResizeAspectFill;
            break;
        case ResizeAspect:
            //按原视频比例显示,是竖屏的就显示出竖屏的,两边留黑
            _videoFillMode = AVLayerVideoGravityResizeAspect;
            break;
        case Resize:
            //拉伸视频内容达到边框占满,但不按原比例拉伸
            _videoFillMode = AVLayerVideoGravityResize;
            break;
    }
}
//MARK: - 是否自动支持全屏
- (void)setAutoFullScreen:(BOOL)autoFullScreen{
    _autoFullScreen = autoFullScreen;
}
//MARK: - 是否支持横屏
-(void)setIsLandscape:(BOOL)isLandscape{
    _isLandscape = isLandscape;
    _landscape   = isLandscape;
}
//MARK: - 重复播放
- (void)setRepeatPlay:(BOOL)repeatPlay{
    _repeatPlay = repeatPlay;
}

//MARK: - 传入播放地址
- (void)setUrl:(NSURL *)url{
    if (_url == url) {
        return;
    }
    
    _url = url;
    
    //
    self.playerItem = [AVPlayerItem playerItemWithAsset:[AVAsset assetWithURL:_url]];
    
    //判断当前的
    if (self.player.currentItem) {
        [self.player replaceCurrentItemWithPlayerItem:self.playerItem];
    }else{
        self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
    }
    //
    _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
    
    //全屏拉伸
    _playerLayer.videoGravity = AVLayerVideoGravityResize;
    
    //设置静音模式播放声音
    AVAudioSession * session  = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    [session setActive:YES error:nil];
    if (_videoFillMode){
        _playerLayer.videoGravity = _videoFillMode;
    }
    
    //放到最下面,防止遮挡
    [self.layer insertSublayer:_playerLayer atIndex:0];
    [self setNeedsLayout];
    [self layoutIfNeeded];
    
    
    //播放
    [self playVideo];
}
-(void)setPlayerItem:(AVPlayerItem *)playerItem{
    _playerItem = playerItem;
    
    //当播放结束, 去做一些其它的操作
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
    
    //预播放状态,有三种情况
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    
    //已经缓冲的值
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    
    //seekToTime后,缓冲数据为空,而且有效时间内数据无法补充,播放失败
    [playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
    
    //seekToTime后,可以正常播放,相当于readyToPlay,一般拖动滑竿菊花转,到了这个这个状态菊花隐藏
    [playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
}

//
- (void)setState:(CLPlayerState)state{
    _state = state;
    if (state == CLPlayerStateBuffering) {//缓冲中

        [self.playerMenuView.activity startAnimating];
    }else if (state == CLPlayerStateFailed){//加载失败
        
        [self.playerMenuView.activity stopAnimating];
        self.playerMenuView.failButton.hidden = NO;
    }else{//暂停, 结束, 播放中
        
        [self.playerMenuView.activity stopAnimating];
        if (_isUserPlay) {
            [self playVideo];
        }
    }
}

//MARK: - 隐藏或者显示状态栏方法
- (void)setStatusBarHidden:(BOOL)hidden{
    //取出当前控制器的导航条
    UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
    //设置是否隐藏
    statusBar.hidden  = hidden;
}

//MARK: - 监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    
    if ([keyPath isEqualToString:@"status"]) {
        
        if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
            self.state = CLPlayerStatePlaying;
        } else if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
            self.state = CLPlayerStateFailed;
        }
    } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        
        // 计算缓冲进度
        NSTimeInterval timeInterval = [self availableDuration];
        CMTime duration             = self.playerItem.duration;
        CGFloat totalDuration       = CMTimeGetSeconds(duration);
        [self.playerMenuView.progress setProgress:timeInterval / totalDuration animated:NO];
        
    } else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
        
        // 当缓冲是空的时候
        if (self.playerItem.playbackBufferEmpty) {
            self.state = CLPlayerStateBuffering;
            [self bufferingSomeSecond];
        }
        
    } else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
        
        // 当缓冲好的时候
        if (self.playerItem.playbackLikelyToKeepUp && self.state == CLPlayerStateBuffering){
            self.state = CLPlayerStatePlaying;
        }
        
    }
}

#pragma mark - 缓冲较差时候
//卡顿时会走这里
- (void)bufferingSomeSecond{
    self.state = CLPlayerStateBuffering;
    
    __block BOOL isBuffering = NO;
    if (isBuffering) {
        return;
    }
    isBuffering = YES;
    
    // 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来
    [self pausePlay];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self playVideo];
        // 如果执行了play还是没有播放则说明还没有缓存好,则再次缓存一段时间
        isBuffering = NO;
        if (!self.playerItem.isPlaybackLikelyToKeepUp) {
            [self bufferingSomeSecond];
        }
        
    });
}
//计算缓冲进度
- (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 - 播放完成
- (void)moviePlayDidEnd:(id)sender{
    if (_repeatPlay){
        //重播
        [self resetPlay];
    } else {
        //暂停
        [self pausePlay];
    }
    //显示重播按钮
    self.playerMenuView.repeatButton.hidden = NO;
    
    //
    if ([self.delegate respondsToSelector:@selector(endPlay)]) {
        [self.delegate endPlay];
    }
}


#pragma mark - PlayerMenuDelegate
//返回按钮代理
- (void)backButtonAction:(UIButton *)sender{
    if ([self.delegate respondsToSelector:@selector(clickMenuBackButton)]) {
        [self.delegate clickMenuBackButton];
    }
}
//播放按钮
-(void)playButtonAction:(UIButton *)sender{
    if (sender.selected){
        [self playVideo];
    } else {
        [self pausePlay];
    }
}
//全屏按钮代理
- (void)fullButtonAction:(UIButton *)sender{
    _isLandscape = NO;
    if (_isFullScreen){
        [self originalscreen];
    } else {
        [self fullScreenWithDirection:Letf];
    }
    _isLandscape = _landscape;
}
//开始滑动
- (void)progressSliderTouchBegan:(PlayerSlider *)sender{
    //暂停
    [self pausePlay];
    [self destroyTimer];
}
//滑动中
- (void)progressSliderValueChanged:(PlayerSlider *)sender{
    //计算出拖动的当前秒数
    CGFloat total = (CGFloat)_playerItem.duration.value / _playerItem.duration.timescale;
    NSInteger dragedSeconds = floorf(total * sender.value);
    
    //转换成CMTime才能给player来控制播放进度
    CMTime dragedCMTime = CMTimeMake(dragedSeconds, 1);
    
    [_player seekToTime:dragedCMTime];
}
//滑动结束
- (void)progressSliderTouchEnded:(PlayerSlider *)sender{
    //继续播放
    [self playVideo];
    _timer = [NSTimer scheduledTimerWithTimeInterval:DisappearTime target:self selector:@selector(disappear) userInfo:nil repeats:NO];
}
//失败按钮代理
- (void)failButtonAction:(UIButton *)sender{
    [self setUrl:_url];
    [self setNeedsLayout];
    [self layoutIfNeeded];
}
//重播按钮
- (void)repeatButtonAction:(UIButton*)sender{
    [self resetPlay];
}


#pragma mark - 控制
//播放
- (void)playVideo{
    _isUserPlay = YES;
    self.playerMenuView.playButton.selected = YES;
    [_player play];
}
//暂停播放
- (void)pausePlay{
    self.playerMenuView.playButton.selected = NO;
    [_player pause];
}
//重新开始播放
- (void)resetPlay{
    [_player seekToTime:CMTimeMake(0, 1)];
    [self playVideo];
}
//销毁播放器
- (void)destroyPlayer{
    //销毁定时器
    [self destroyAllTimer];
    [self removeObserver];
    //暂停
    [_player pause];
    //清除
    [self.player setRate:0];
    [self.player replaceCurrentItemWithPlayerItem:nil];
    self.playerItem = nil;
    self.player = nil;
}
-(void)removeObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
    [_playerItem removeObserver:self forKeyPath:@"status"];
    [_playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
    [_playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
    [_playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
}


//点击屏幕
- (void)disappearAction:(UIButton *)button{
    //取消定时消失
    [self destroyTimer];
    
    //
    if (_isDisappear){
        //添加定时消失
        _timer = [NSTimer scheduledTimerWithTimeInterval:DisappearTime target:self selector:@selector(disappear) userInfo:nil repeats:NO];
        
        [UIView animateWithDuration:0.5 animations:^{
//            self.playerMenuView.topToolBar.alpha    = 1.0;
            self.playerMenuView.bottomToolBar.alpha = 1.0;
        }];
    } else {
        [UIView animateWithDuration:0.5 animations:^{
//            self.playerMenuView.topToolBar.alpha    = 0;
            self.playerMenuView.bottomToolBar.alpha = 0;
        }];
    }
    _isDisappear = !_isDisappear;
}



#pragma mark - 全屏小屏
//原始大小
- (void)originalscreen{
//    [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationPortrait] forKey:@"orientation"];
    
    if ([self.delegate respondsToSelector:@selector(autoFullScreen:)]) {
        [self.delegate autoFullScreen:UIInterfaceOrientationPortrait];
    }
    
    _isFullScreen = NO;
    [self setStatusBarHidden:NO];
    [UIView animateWithDuration:0.25 animations:^{
        //还原
        self.transform = CGAffineTransformMakeRotation(0);
    }];
    self.frame = _customFarme;
    //还原到原有父类上
    [_fatherView addSubview:self];
    self.playerMenuView.fullButton.selected = NO;
    self.playerMenuView.topToolBar.hidden = self.isShowTopTool;
}
//全屏
- (void)fullScreenWithDirection:(Direction)direction{
    if ([self.delegate respondsToSelector:@selector(autoFullScreen:)]) {
        [self.delegate autoFullScreen:UIInterfaceOrientationLandscapeRight];
    }
    
    //记录播放器父类
    _fatherView = self.superview;
    //记录原始大小
    _customFarme = self.frame;
    _isFullScreen = YES;
    [self setStatusBarHidden:YES];
    //添加到Window上
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    [keyWindow addSubview:self];
    
    if (_isLandscape){
        self.frame = CGRectMake(0, 0, KScreen_Width, KScreen_Height);
    } else {
        if (direction == Letf){
            [UIView animateWithDuration:0.25 animations:^{
                self.transform = CGAffineTransformMakeRotation(M_PI / 2);
            }];
        } else {
            [UIView animateWithDuration:0.25 animations:^{
                self.transform = CGAffineTransformMakeRotation( - M_PI / 2);
            }];
        }
        self.frame = CGRectMake(0, 0, KScreen_Height, KScreen_Width);
    }
    self.playerMenuView.fullButton.selected = YES;
    [self setNeedsLayout];
    [self layoutIfNeeded];
}



@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容