iOS-AVPlayer视频播放封装

JJPlayerView

AVPlayer依赖于AVFoundation,另外AVPlayer不包含UI,想要完成播放功能,需要自己写UI。

项目中往往会用到视频播放功能,GitHub上面也有很多star很高的库也能满足需求,但是当我们需要高度自定义UI的时候,他们却不能满足要求。

所以对于要求较高的播放功能,我们需要自己封装一个播放器。
今天我们就来实现一个简单的播放器,目的是了解播放器的使用。

iOS开发中会有音视频播放的需求。目前常用的音视频播放器有 AVPlayer,AVPlayer支持本地播放,分步下载,在线流媒体播放。

👇 👇 👇 👇 👇 👇 👇

AVPlayer基础知识简介

  1. AVPlayer是用于管理媒体资源播放的控制器,它提供了播放器的诸多功能:播放、暂停、倍速等。AVPlayer一次只能播放一个资源文件(本地或者远程)。
    AVQueuePlayer可以播放一个列表,是AVPlayer的子类。
    然后我们根据视频资源,创建我们需要的AVPlayer。系统为我们也提供了几个初始化方法。
+ (instancetype)playerWithURL:(NSURL *)URL;
+ (instancetype)playerWithPlayerItem:(nullable AVPlayerItem *)item;
- (instancetype)initWithURL:(NSURL *)URL;
- (instancetype)initWithPlayerItem:(nullable AVPlayerItem *)item;
  1. AVPlayerItem代表了一个播放资源,每播放一个视频需要一个数据资源,我们需要初始化一个AVPlayerItem。
    系统提供了几个初始化AVPlayerItem的方法。
/// - AVPlayerItem的初始化方法
+ (instancetype)playerItemWithURL:(NSURL *)URL;
+ (instancetype)playerItemWithAsset:(AVAsset *)asset;
- (instancetype)initWithURL:(NSURL *)URL;
- (instancetype)initWithAsset:(AVAsset *)asset;
  1. 单纯使用AVPlayer是无法显示视频的,需要将视频添加至AVPlayerLayer上,然后添加到我们自己创建的视图layer上面。

封装JJPlayerView

封装播放器可以分为两个部分:

  • 第一是播放视频的功能
  • 第二个是UI交互。
    两个部分不是完全分开的,UI就是调用这个方法,达到播放视频的功能。
  1. 创建一个JJPalyerView,作为承载视图。
  2. 创建必要的属性
    AVPlayer,AVPlayerItem,AVPlayerLayer
/// 播放器
@property (nonatomic, strong)   AVPlayer         *player;
/// 播放器item
@property (nonatomic, strong)   AVPlayerItem     *playerItem;
/// 播放器layer
@property (nonatomic, strong)   AVPlayerLayer    *playerLayer;
/// 控件的原始frame
@property (nonatomic, assign)   CGRect           customFrame;
/// 父类控件
@property (nonatomic, strong)   UIView           *fatherView;
/// 视图拉伸模式
@property (nonatomic, copy)     NSString         *fillMode;
/// 是否处于全屏状态
@property (nonatomic, assign)   BOOL             isFullScreen;
/// 工具条是否隐藏
@property (nonatomic, assign)   BOOL             isDisappear;
/// 用户播放标记
@property (nonatomic, assign)   BOOL             isUserPlay;
/// 点击最大化标记
@property (nonatomic, assign)   BOOL             isUserTapMaxButton;
/// 是否播放完毕
@property (nonatomic, assign)   BOOL             isFinish;
/// 是否在调节音量:YES为音量,NO为屏幕亮度
@property (nonatomic, assign)   BOOL isVolume;
/// 是否在拖拽
@property (nonatomic, assign)   BOOL isDragged;
/// 缓冲
@property (nonatomic, assign)   BOOL isBuffering;
/// 用来保存快进的总时长
@property (nonatomic, assign)   CGFloat sumTime;
/// 播放器配置信息
@property (nonatomic, strong) JJPlayerConfigure *playerConfigure;
/// 视频播放控制面板(遮罩)
@property (nonatomic, strong) JJPlayerMaskView  *playerMaskView;
/// 滑动方向
@property (nonatomic, assign) JJPanDirection     panDirection;
/// 音量滑杆
@property (nonatomic, strong) UISlider *volumeViewSlider;
/// 点击屏幕定时器
@property (nonatomic, strong) NSTimer *tapTimer;
/// 播放器的播放状态
@property (nonatomic, assign) JJPlayerState playerState;
/// 记录点击屏幕定时器的时间
@property (nonatomic, assign)   NSInteger tapTimeCount;
/// 是否已经移除了KVO
@property (nonatomic, assign)   BOOL isRemoveObserver;
  1. 创建播放完成和返回按钮回调事件
/// 返回按钮回调
@property (nonatomic, copy) void(^BackBlock) (UIButton *backButton);
/// 播放完成回调
@property (nonatomic, copy) void(^EndBlock)(void);
  1. 添加通知,监听播放器的各种状态
//开启
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    /// 监听横竖屏的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
    /// 进入后台
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil];
    /// 进入前台
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayground:) name:UIApplicationDidBecomeActiveNotification object:nil];
    
    // 添加播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
    // 添加打断播放的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interruptionComing:) name:AVAudioSessionInterruptionNotification object:nil];
    // 添加插拔耳机的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil];
  1. 将AVPlayerLayer添加到视图上
    定义视频在AVPlayerLayer中的显示方式,默认方式AVLayerVideoGravityResizeAspect;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
playerLayer.frame = frame;
playerLayer.backgroundColor = [UIColor blackColor].CGColor;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[view.layer addSublayer: playerLayer];

我们看到了,需要设置AVPlayerLayerframe,还需要通过videoGravity设置资源填充展示的样式.

但是这里有更好的写法,创建一个PlayerLayerView,将这个PlayerLayerView的layer返回class,设置为AVPlayerLayer类型,这样就不用设置layer的frame了,直接设置view的frame就可以,更加直观方便。

@interface JJPlayerLayer : UIView

@end

@implementation JJPlayerLayer

+ (Class)layerClass {
    return AVPlayerLayer.class;
}

@end

/// 播放器layer
@property (nonatomic, strong)   JJPlayerLayer    *playerLayer;
  1. AVPlayer提供了音视频的播放和暂停功能,与之对应的方法: playpause
/// 开始播放
- (void)play{
     // 这里不只是播放,点击播放的时候还要设置相关UI,判断当前状态是否可以播放。并不是直接播放就可以。
     // 判断当前是否播放完成,播放完成后要从头开始播放
    self.playerMaskView.playButton.selected = YES;
     // [self.layer insertSublayer:self.playerLayer atIndex:0];
    if (self.isFinish && self.playerMaskView.slider.value == 1) {
        _isFinish = NO;
        [_player seekToTime:CMTimeMake(0, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
    } else {
        [self.player play];
    }
}
/// 暂停
- (void)pause{
    /// 设置暂停相关UI样式。
    [self.player pause];
}
  1. 播放器一次只能给你管理一个播放资源,但我们需要切换资源的时候,可以调用相关replaceCurrentItemWithPlayerItem方法。AVFoundation框架还提供了一个专门的类:AVQueuePlayer,用来管理资源列表,我们暂时不讨论,后面再延伸.
- (void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;

但是我们为了更方便使用,当切换资源的时候,重置播放器。也就是销毁原来的资源,重新创建。

#pragma mark - 重置播放器
- (void)resetPlayer{
    //重置状态
    self.playerState = JJPlayerStatePause;
    _isUserPlay = YES;//用户点击标志
    _isDisappear = NO;//工具条隐藏标记
    //移除之前的
    [self pause];//先暂停
    [self.playerLayer removeFromSuperlayer];
    self.playerLayer = nil;
    self.player = nil;
    //还原进度条和缓冲条
    self.playerMaskView.slider.value = 0;
    self.playerMaskView.progressView.progress = 0;
    //重置时间
    self.playerMaskView.currentTimeLabel.text = @"00:00";
    self.playerMaskView.totalTimeLabel.text = @"00:00";

    [self destoryToolBarTimer];
    //重置Toolbar
    [UIView animateWithDuration:0.25 animations:^{
        self.playerMaskView.topToolBar.alpha = 1.0;
        self.playerMaskView.bottomToolBar.alpha = 1.0;
    }];
    //重新添加工具条消失定时器
    [self resetTooBarDisappearTimer];
    self.playerMaskView.failButton.hidden = YES;
    //开始转子动化
    [self.playerMaskView.loadingView startAnimation];
}

然后再重新添加新的资源

   self.playerItem = [AVPlayerItem playerItemWithURL:_url];
    //创建播放器
    _player = [AVPlayer playerWithPlayerItem:_playerItem];
    _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
    _playerLayer.videoGravity = _fillMode;
    [self.layer insertSublayer:_playerLayer atIndex:0];
  1. 监听资源的状态
    我们可以使用KVO监听playerItem的几个属性statusloadedTimeRangesplaybackBufferEmpty
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
  • 只有当status发生改变,并且值为AVPlayerItemStatusReadyToPlay的时候,说明视频资源准备完成,才可以播放。
    结果是AVPlayerItemStatusFailed的时候,说明解析失败,需要展示相关UI和文案。

  • loadedTimeRanges可以计算缓冲进度

  • playbackBufferEmpty说明当前缓冲为空,需要做暂停处理,加载一会...........

- (void)setPlayerItem:(AVPlayerItem *)playerItem{
    if (_playerItem == playerItem) {
        return;
    }
    
    if (_playerItem) {
        if (!self.isRemoveObserver) {
            [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
            [_playerItem removeObserver:self forKeyPath:@"status"];
            [_playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
            [_playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
        }
        self.isRemoveObserver = YES;
        //重置播放器
        [self resetPlayer];
    }
    _playerItem = playerItem;
    if (playerItem) {
        self.isRemoveObserver = NO;
        [[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];
        [playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
    }
}
#pragma mark - kvo监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"status"]) {
        if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {//加载完成,可以播放
            //加载完成后,再添加平移手势
            //添加平移手势,用来控制音量/亮度/快进快退
            UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureDirection:)];
            pan.delegate = self;
            pan.maximumNumberOfTouches = 1;  //一根手指
            pan.delaysTouchesBegan = YES;
            pan.delaysTouchesEnded = YES;
            pan.cancelsTouchesInView = YES;
            [self.playerMaskView addGestureRecognizer:pan];
            self.player.muted = self.playerConfigure.isMute;
            [self addPeriodicTimeObserver];
            if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
                self.playerState = JJPlayerStatePlaying;
            }else if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
                self.playerState = JJPlayerStateFailed;
            }
            
        }else if (self.player.currentItem.status == AVPlayerItemStatusFailed){
            // 解析失败(播放失败)
            self.playerState = JJPlayerStateFailed;
        }
    }else if ([keyPath isEqualToString:@"loadedTimeRanges"]){
        //计算缓冲进度
        NSTimeInterval timeInterval = [self calculateBufferProgress];
        CMTime duration = self.playerItem.duration;
        CGFloat totalDuratuon = CMTimeGetSeconds(duration);
        [self.playerMaskView.progressView setProgress:(timeInterval / totalDuratuon) animated:NO];
    }else if ([keyPath isEqualToString:@"playbackBufferEmpty"]){
        //当前缓冲时空的时候
        if (self.playerItem.isPlaybackBufferEmpty) {
            self.playerState = JJPlayerStateBuffering;
            [self bufferingSomeSecond];//卡顿一会,缓冲几秒
        }
    }
}

我们可以看到添加KVO之前。先移除,是因为我们可能会替换资源,如果不移除,监听到的就是之前的资源信息。

  1. 添加通知监听其他状态,实现UI交互功能
    其实这个时候其实就一个可以将视频播放出来了,但是我们为了更好地体验,需要获取到播放器的状态:播放、加载、播放完成等等。

这里我们可以通过通知来获取当前的具体状态。
通过通知AVPlayerItemDidPlayToEndTimeNotification可以接收到视频播放完成通知.
通过通知AVAudioSessionInterruptionNotification可以接收到打断播放的通知.
通过通知AVAudioSessionRouteChangeNotification可以接收到插拔耳机的通知.

 // 添加播放完成通知
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
 // 添加打断播放的通知
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interruptionComing:) name:AVAudioSessionInterruptionNotification object:nil];
 // 添加插拔耳机的通知
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil];
  1. AVPlayer 中播放进度的获取如下,通过回传的time参数,可以更新进度.
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;

当拿到回传的时间,我们需要更新进度指示器。

@weakify(self);
    [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 10) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        @strongify(self);
        [self sliderTimerAction];
    }];

- (void)sliderTimerAction{
    if (self.playerItem.duration.timescale != 0) {
        self.playerMaskView.slider.maximumValue = 1;
        CGFloat total = _playerItem.duration.value / _playerItem.duration.timescale;
        self.playerMaskView.slider.value = CMTimeGetSeconds(self.playerItem.currentTime) / total;
        //判断是否正在播放
        if (self.playerItem.isPlaybackLikelyToKeepUp && self.playerMaskView.slider.value > 0) {
            self.playerState = JJPlayerStatePlaying;
        }
        
        //当前时长
        NSInteger proMin = (NSInteger)CMTimeGetSeconds([_player currentTime]) / 60;//当前分钟
        NSInteger proSec = (NSInteger)CMTimeGetSeconds([_player currentTime]) % 60;//当前秒
        self.playerMaskView.currentTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", (long)proMin, (long)proSec];
        //总时长
        NSInteger durMin = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale / 60;//总分钟
        NSInteger durSec = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale % 60;//总秒
        self.playerMaskView.totalTimeLabel.text   = [NSString stringWithFormat:@"%02ld:%02ld", (long)durMin, (long)durSec];
    }
}
  1. 有的时候用户可以快进,或者手动拖动到指定时间点播放资源:
- (void)seekToTime:(CMTime)time;

当我们滑动播放器进度的时候,或者活动屏幕的时候,可以调用次方法,改变播放进度。

  1. 其他通知的监听
    电话打断播放,home键回到主页面,屏幕翻转等等:
  /// 添加打断播放的通知
  [[NSNotificationCenter defaultCenter] addObserver: self selector:@selector(interruptionComing:) name: AVAudioSessionInterruptionNotification object:nil];
  /// 监听横竖屏的通知
  [[NSNotificationCenter defaultCenter] addObserver: self selector:@selector(orientationChanged:) name: UIDeviceOrientationDidChangeNotification object:nil];
  /// 进入后台
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil]
  /// 进入前台
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayground:) name:UIApplicationDidBecomeActiveNotification object:nil];
  1. 创建一个UISlider,作为食品播放器的滑竿。
    系统的滑竿是非常好用的,但是UI不满足要求,我们继承系统UISlider,稍作修改:
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface JJSlider : UISlider

@end

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

- (void)setupUI {
    UIImage *thumbImage = [UIImage imageWithName:@"JJRoundButton"];
    [self setThumbImage:thumbImage forState:UIControlStateHighlighted];
    [self setThumbImage:thumbImage forState:UIControlStateNormal];
}
@end

修改滑竿的按钮图片即可。

当拖动滑竿的时候改变视频播放的进度。

  1. 当可以播放的时候,通过参数rate可以获取到当前是在加载中还是播放中。也可以通过它设置播放速度。
  2. 我们需要创建一个遮罩层,添加一些基本的功能,比如,胡奥明屏幕实现快进,快退功能,改变音量,改变屏幕亮度,还要添加一些标题,播放按钮等功能。
    我们创建一个JJPlayerMaskView作为遮罩层。
    遮罩层里面包含的属性:标题,返回按钮,播放按钮,暂停按钮,进度条,缓冲条,播放失败提示,全屏按钮等等。

页面上需要添加手势,点击隐藏/显示 这些控件。
创建一个协议,将这些事件传递出去。

@protocol JJPlayerMaskViewDelegate <NSObject>

/// 返回按钮点击事件代理
- (void)jj_playerMaskViewBackButtonAction:(UIButton *_Nonnull)button;
/// 播放按钮点击事件代理
- (void)jj_playerMaskViewPlayButtonAction:(UIButton *_Nonnull)button;
/// 全屏按钮点击事件代理
- (void)jj_playerMaskViewFullButtonAction:(UIButton *_Nonnull)button;
/// 开始滑动
- (void)jj_playerMaskViewProgressSliderTouchBegan:(JJSlider *_Nullable)slider;
/// 滑动中
- (void)jj_playerMaskViewProgressSliderTouchChanged:(JJSlider *_Nullable)slider;
/// 滑动结束
- (void)jj_playerMaskViewProgressSliderTouchEnd:(JJSlider *_Nullable)slider;
/// 播放失败按钮事件
- (void)jj_playerMaskViewFailButtonAction:(UIButton *_Nonnull)button;
/// 代理事件
@property (nonatomic, weak)   id<JJPlayerMaskViewDelegate > delegate;
/// 顶部工具条
@property (nonatomic, strong) UIView              *topToolBar;
/// 标题
@property (nonatomic, strong) UILabel             *titleLabel;
/// 底部工具条
@property (nonatomic, strong) UIView              *bottomToolBar;
/// 加载动画
@property (nonatomic, strong, nullable) JJAnimationView     *loadingView;
/// 顶部工具条返回按钮(会和工具条一起消失显示)
@property (nonatomic, strong) UIButton            *backButton;
/// 顶部返回按钮,一直显示
@property (nonatomic, strong) UIButton            *backOnlyButton;


@property (nonatomic, strong) UIButton            *lockButton;

/// 底部工具条播放/暂停按钮
@property (nonatomic, strong) UIButton            *playButton;
/// 底部工具条全屏按钮
@property (nonatomic, strong) UIButton            *fullButton;
/// 底部工具条当前时间
@property (nonatomic, strong) UILabel             *currentTimeLabel;
/// 底部工具条总时间
@property (nonatomic, strong) UILabel             *totalTimeLabel;
/// 缓冲进度条
@property (nonatomic, strong) UIProgressView      *progressView;
/// 播放进度条
@property (nonatomic, strong) JJSlider            *slider;
/// 加载失败按钮
@property (nonatomic, strong) UIButton            *failButton;
  1. 判断滑动方向,并记录
    水平滑动的时候
if (self.panDirection == JJPanDirectionHorizontalMoved) {
    [self panHorizontalMoved:thePoint.x];
}

水平滑动的时候改变slider的UI,并调节进度

#pragma mark - 水平滑动,调节进度
- (void)panHorizontalMoved:(CGFloat)value{
    //水平滑动进度,逻辑多,多做一些判断
    if (value == 0) {
        return;
    }
    //每次滑动时间需要叠加.
    self.sumTime += value/200.0;
    //需要给sumTime限制范围
    CMTime totalTime = self.playerItem.duration;
    CGFloat totalMovieDuration = (CGFloat)(totalTime.value/totalTime.timescale);
    if (self.sumTime > totalMovieDuration) {
        self.sumTime = totalMovieDuration;
    }
    if (self.sumTime < 0) {
        self.sumTime = 0;
    }
    self.isDragged = YES;
    //计算出拖动的当前秒数
    CGFloat dragedSeconds = self.sumTime;
    //滑杆进度
    CGFloat sliderValue = dragedSeconds/totalMovieDuration;
    //设置滑杆
    self.playerMaskView.slider.value = sliderValue;
    
    //转换成CMTime才能player来控制进度
    CMTime dragedCMTime = CMTimeMake(dragedSeconds, 1);
    [self.player seekToTime:dragedCMTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
    NSInteger proMin = (NSInteger)CMTimeGetSeconds(dragedCMTime) / 60;//当前秒
    NSInteger proSec = (NSInteger)CMTimeGetSeconds(dragedCMTime) % 60;//当前分钟
    self.playerMaskView.currentTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld",proSec,proMin];  
}
  1. 创建音量滑竿
/// 音量滑杆
@property (nonatomic, strong) UISlider *volumeViewSlider;

当垂直滑动的时候,以中心线为标准,判断滑动位置,一侧改变音量,另一侧改变屏幕亮度

if (self.panDirection == JJPanDirectionVerticalMoved){
    [self panVerticalMoved:thePoint.y];
}
#pragma mark - 垂直滑动,调节音量和亮度
- (void)panVerticalMoved:(CGFloat)value{
    if (self.isVolume) { //音量
        self.volumeViewSlider.value -= value/10000.0;
    }else{ //亮度
        [UIScreen mainScreen].brightness -= value/10000.0;
    }
}
  1. 全屏处理。
    视频播放一般有全屏播放的功能。
    我们做法是,全屏的时候将播放器添加至keyWindow上面,并记录原来父视图,返回时,添加到圆的父视图上面。
    点击全屏的时候
- (UIWindow *)getKeyWindow{
    if (@available(iOS 13.0, *)){
        return [UIApplication sharedApplication].windows.lastObject;
    }else{
        return [UIApplication sharedApplication].keyWindow;
    }
}

//添加到keyWindow上
 UIWindow *keyWindow = [self getKeyWindow];
 [keyWindow addSubview:self];
 [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationLandscapeRight] forKey:@"orientation"];

只设置翻转一个方向即可!

还原的时候:

[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationPortrait] forKey:@"orientation"];
self.frame = _customFrame;
// 还原到原有父类上
[_fatherView addSubview:self];
self.playerMaskView.fullButton.selected = NO;
  1. 其他细节处理
    锁屏功能,点击后页面不再处理事件。
    这个功能简单,记录锁屏按钮状态,处理各事件前,判断锁屏状态。或者锁屏的时候,将控件隐藏起来。

弹幕功能,在遮罩上面的一部分区域,添加弹幕即可。

demo地址

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

推荐阅读更多精彩内容